react基础
先别急着看技术,看看美图摸鱼一手🤔。
1. 前言
- 随着前端世界的蓬勃发展,伟大的人类创造了react(
好好好,你小子vue一句不提),随着react,vue,angular的出现,使得前端世界进入三国分裂时期。当然在国内只有vue与react,angular玩的人很少。 - 什么是react? react.js是用于构建用户界面的 一个js 库,相较于vue2而言,更为贴近原生事件。
- react有什么用?React 提供了组件式的开发、声明式的界面和响应式 DOM 更改(虚拟dom),这让我们可以😌更简洁的出完美的项目。
2. 类组件与函数组件的基本结构
- 类组件
1.须继承React.Component
2.必须实现render()方法
3.构造函数constructor()可选:如果不写constructor,会使用默认构造函数
4.使用箭头函数不需要将函数bind在constructor下
5.super() 和 super(props),如果super() 中不传入props,this.props会输出undefined,
注意:super关键字,它指代父类的实例(即指代父类的this对象),子类在没有自己的this对象情况下,去继承父类的this对象使用。子类需要在constructor方法中调用super方法,从而得到父类的this对象,否则会报错。简单来说,就是需要使用this,就得加上super🤪
// 默认构造函数
constructor(props, context) {
super(props, context);
}
// 类组件基本结构
import React from "react";
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "React",
number: 0,
};
this.noBind = this.noBind.bind(this)
}
componentDidMount() {}
handleClick = () => { // 使用箭头函数不需要将函数bind在constructor下
this.setState((state, props) => {
return { number: this.state.number+1 };
});
};
noBind(){
this.setState((state, props) => {
return { number: this.state.number+1 };
});
}
render() {
return (
<div>
{this.state.name}: {this.state.number}
<button onClick={this.handleClick}>number++</button>
<button onClick={this.noBind}>number++</button>
</div>
);
}
}
export default ClassComponent;
- 函数组件
1.与类组件不同,函数组件不需要继承React.Component
2.使用Hooks管理状态,如useState
3.函数组件使用useEffect Hook来模拟生命周期方法
4.函数组件可以接收props作为参数
import React,{ useState } from "react";
export default function Func(params) {
// const [state, setState] = useState("state");
const {state,changeState} = params
useEffect(() => {
// 相当于 componentDidMount 和 componentDidUpdate
return () => {
// 清理函数,相当于 componentWillUnmount
};
}, []);
const changeState = () => {
setState("change");
};
return (
<div>
<div>{state}</div>
<button onClick={changeState}>changeState</button>
</div>
);
}
总结:
1. 定义方式:类组件使用class关键字,函数组件是普通函数。
2. this关键字:类组件可以使用this,函数组件没有this。
3. 状态管理:类组件使用this.state和this.setState(),函数组件使用useState Hook,react训练通关之常用hook。
4. 生命周期:类组件有完整的生命周期方法,函数组件使用useEffect Hook模拟生命周期。
5. props访问:类组件通过this.props访问,函数组件直接通过参数访问。
3. 状态(State)
1.类组件中的setState
setState(obj,callback)
1. obj:当obj为一个对象,则为即将合并的 state ;
obj 是一个函数,那么当前组件的 state 和 props 将作为参数,返回值用于合并新的 state。
2. callback:callback 为一个函数,函数执行上下文中可以获取当前 setState 更新后的最新 state 的值,可以作为依赖 state 变化的副作用函数,可以用来做一些基于 DOM 的操作。
3. setState()有同步有异步,基本上都是异步更新,自己定义的DOM事件里setState()是同步的
2.函数组件中的useState
[ state , dispatch ] = useState(initData)
1. state,视图渲染的数据。
2. dispatch作为改变state的回调,用于对state值的更新。
3. initData 有两种情况,
第一种情况是非函数,作为state的初始值。
第二种情况是函数,将函数返回后的值作为state的初始值。
这是一道常见的面试题下🤣:state 到底是同步还是异步的?
一般的思路都是围绕batchUpdate 批量更新概念展开的,答出了批量更新被打破的条件,以及概念基本就可以解决这道面试题。
- 当我们进行setState时,其实对于react而言,就是调用了内部的 enqueueSetState方法进行update事件的创建,然后将事件放入当前 fiber 对象的待更新队列中,最后进行更新。
- react内部通过调用batchedEventUpdates识别是否开启批量更新,通过控制isBatchingEventUpdates的状态进行更新。
简单来说
-
对于开启isBatchingEventUpdates=true的事件,表现为同步更新
如:生命周期、React 中注册的事件 -
对于不开启的事件(不是在 React 中注册的事件):表现为异步更新
setTimeout/setInterval等、自定义 DOM 事件等
说明,setTimeout执行时,会先将函数推入任务队列,此时批量更新会开启,但setTimeout结束时,会将批量更新关闭,然后才执行推入队列中的任务,此时表现为异步
// 异步更新
changeMessage() {
this.setState({
message: "NewMessage"
})
console.log(this.state.message); // message
}
// 同步更新,
changeMessage() {
setTimeout(() => {
this.setState({
message: "NewMessage"
});
console.log(this.state.message); // NewMessage
}, 0);
}
// flushSync 提升更新优先级,flushSync会合并之前的setState
ReactDOM.flushSync(()=>{ this.setState({ number: 3 }) })
4. 属性(Props)
React 的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。
Props作为参数传递的值,承接起了react组件化的数据传递任务。
porps作为输入,主要有以下几种传递类型
- props 作为渲染数据源传递。
- props 作为回调函数传递。
- props 作为组件传递。
- props 作为渲染函数传递。
- props 作为渲染函数使用 children 传递。
- props 作为组件使用 children 传递。
function ChidrenComponent(){
return <div> this is ChidrenComponent </div>
}
class Index extends React.Component{
state={
message: "React"
}
handleClick = () => this.setState({ message:'Hello React' })
render(){
return <div>
<Children
message={this.state.message} // 数据传递
handleClick={ this.handleClick } // 回调函数传递
Component={ ChidrenComponent } // 组件传递
renderName={ ()=><div> this is React </div> } // 作为函数传递
>
{ ()=> <div>this is renderReact</div> } --->children[0]
<ChidrenComponent /> --->children[1]
</Children>
</div>
}
}
// 子组件中通过以下方式
const { children, message, renderName, handleClick, Component } = this.props;
注意,props的抽离,与props的注入
// props的抽离
props = { meaasge: "react", number: 2,};
组件中使用 const { number, ...fatherProps } = props 将number抽离
return <Son {...fatherProps} />; 子组件中获取到props为{ meaasge: "react"}
// 隐式注入
使用React.cloneElement(prop.children,{ meaasge:'this is React !' })进行props.chidren克隆与混入新的 props
5. 生命周期方法
-
react16.3以前的生命周期:
主要分为 初始化阶段、更新阶段、卸载组件1.初始化阶段 constructor->componentWillMount->render->componentDidMount 2.更新阶段 componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate-> render->componentDidUpdate 3.卸载组件 componentWillUnmount -
react16.4及以后三个阶段主要有以下改变
1.创建阶段 constructor---->getDerivedStateFromProps---->render---->componentDidMount 2.更新阶段 getDerivedStateFromProps---->sholdComponentUpdate--->render---->getSnapshotBeforeUpdate --->componentDidupdate 3.阶段 componentWillUnmount
3.新旧生命周期对比
-
生命周期详细介绍react 训练通关生命周期
-
旧版生命周期: 16.3之前的版本包含以下生命周期方法:
componentWillMount()componentWillReceiveProps(nextProps)componentWillUpdate(nextProps, nextState)
-
新版生命周期: 从16.3开始,React废弃了上述三个生命周期方法,并引入了以下新方法:
componentWillMount()去除getDerivedStateFromProps(nextProps, prevState): 用于在渲染之前根据新的props更新state。getSnapshotBeforeUpdate(prevProps, prevState): 在更新DOM之前调用,可以捕获一些信息(如滚动位置)
废弃上诉三个周期主要原因是避免误用: 旧的生命周期方法(如componentWillMount和componentWillReceiveProps)经常被误解和滥用,导致不必要的副作用。
6. Refs和DOM
Refs 在计算机中称为弹性文件系统(英语:Resilient File System,简称ReFS)
React中的Refs主要有以下作用- 使用React.createRef()创建refs
- 获取页面上的
DOM节点
class Index extends React.Component{
constructor(props){
super(props)
this.currentDom = React.createRef(null)
}
componentDidMount(){
console.log(this.currentDom) // 输出当前dom,currentDom
}
render= () => <div ref={ this.currentDom }>currentDom</div>
}
类组件获取 Ref 主要有三种方式
- 字符串, 使用this.refs.xxx 进行实例的获取
- ref对象,使用时,使用
createRef()创建ref,使用时,使用this.myRef.current获取 - 函数, 函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,使用时,直接获取即可
import React from "react";
class Children extends React.Component{
render=()=><div>React,Children</div>
}
// 传入字符串
export default class MyComponent extends React.Component {
componentDidMount() {
console.log(this.refs);// 输出两个dom{myref,currentRef}
}
render = () => (
<div>
<div ref="myref">myref</div>
<Children ref="currentRef" />
</div>
);
}
// 传入对象
export default class Index extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.myRefDom = React.createRef();
}
// myRef = React.createRef()
// myRefDom = React.createRef()
componentDidMount() {
console.log(this.myRef.current); // myRef
console.log(this.myRefDom.current); // myRefDom
}
render() {
return (
<div>
<div ref={(node) => (this.myRef = node)}>myRef</div>
<Children ref={(node) => (this.myRefDom = node)} />
</div>
);
}
}
// 传入函数
export default class Index extends React.Component {
constructor(props) {
super(props);
// this.myRef = React.createRef();
// this.myRefDom = React.createRef();
}
myRef = null // 也可使用React.createRef()
myRefDom = null
componentDidMount() {
console.log(this.myRef); // myRef
console.log(this.myRefDom); // myRef
}
render() {
return (
<div>
<div ref={ this.myRef }>myRef</div>
<Children ref={this.myRefDom} />
</div>
);
}
}
示例结果如下图
函数组件使用 useRef 获取 Ref
const Index = (props) => {
const myref = useRef()
return (
<>
<div ref={myref}></div>
</>
)
}
7. 组件通信
- 父子组件传递传值:通过props
- 子组件向父组件传递数据:通过父组件的回调函数
- 兄弟组件之间通信:通过共同的父组件,进行数据的统一管理
import React, { useState } from "react";
const Children = (props) => {
const { state, changeState } = props;
return (
<div>
<div>{state}</div>
<button onClick={() => changeState()}>changeState</button>
</div>
);
};
export default function father() {
const [state, setState] = useState("state");
const changeState = () => {
setState("change");
};
return <Children changeState={changeState} state={state}></Children>;
}
-
跨多层级组件通信:使用Context API
Context提供了一种在组件树中共享数据的方法,而不必显式地通过每个层级传递props。 -
使用Refs, 可以使用Refs直接访问子组件的实例,从而实现数据等传递与修改。
-
全局状态管理:使用Redux, MobX等状态管理库, 浏览器缓存localStorage等
-
使用react路由传参,进行页面间的数据传递修改
React-dom 17版本以上 useHistory 更新为useNavigate传值
import { useNavigate } from "react-router-dom";
export default function myNavigateTo(){
const navigateTo = useNavigate();
const routerPush = () =>{
navigateTo(`/userinfo?aa=121`,{state: {name:'nickName',sex: 'man',age:23}}) // state传参
// navigateTo({pathname:'/userinfo',search:'?id=1'} // search传参
}
return (
<div>
<button onClick={routerPush}></button>
</div>
)
}
import { useLocation } from "react-router-dom";
export default function userInfo(){
const localtion = useLocation();
console.log(localtion,'---------')
// function useQuery() { // search传参获取参数
// return queryString.parse(localtion.search);
// }
// const query = useQuery()
// console.log(localtion.search) //{id: 1}
return (
<div>
</div>
)
}
8. 高阶组件(HOC)
HOC 解决什么问题,什么时候用到 HOC,以及如何编写 HOC ?
- react的HOC的目的为了解决大量的代码复用,逻辑复用的问题。
- 高阶组件(Higher-order Component)是一个组件(本质上其实是一个函数),它接受一个组件作为参数,并返回一个新的组件。这个新的组件具有一些增强的特性或功能。简单来说,高阶组件就是用来“包装”其他组件,以提供额外的功能或逻辑
// 基础用法
const EnhancedComponent = highOrderComponent(WrappedComponent);
// 向新组件注入一个额外的 `extraProp` 属性
function withExtraProps(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent extraProp="This is an extra prop" {...this.props} />;
}
};
}
高阶组件的用途
- 复用逻辑:HOC 可以在多个组件之间共享相同的逻辑。例如,可以使用 HOC 来处理数据获取、状态管理、权限控制等。
- 条件渲染:HOC 可以根据某些条件决定是否渲染某个组件。
- 修改 Props:HOC 可以在传递给组件的 props 中添加、修改或删除属性。
- 增强组件:HOC 可以为组件添加额外的功能,例如错误边界、性能监控等
// 性能监控
function withPerformanceMonitor(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
this.startTime = performance.now();
}
componentDidUpdate() {
const endTime = performance.now();
console.log(`Render time: ${endTime - this.startTime}ms`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
9. 受控组件与非受控组件
1.受控组件,其数据的流转主要受外部控制,
如以下代码中的username,input组件组件的状态全程响应state控制,
先从this.state进行获取value,再由onChange事件进行 this.setState的数据更新
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
username: ""
}
}
onChange (e) {
console.log(e.target.value);
this.setState({
username: e.target.value
})
}
render () {
return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
}
}
2.非受控组件
非受控组件,就是不受我们控制的组件,他的state由他内部控制
一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态
当需要时,可以使用ref 查询 DOM并查找其当前值,如下:
import React, { Component } from 'react';
export class UnControll extends Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) => {
console.log('input的值为', this.inputRef.current.value);
e.preventDefault();
}
render () {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<input defaultValue="lindaidai" ref={this.inputRef} />
<input type="submit" value="提交" />
</form>
)
}
}
10.Context
Context 通过组件树提供了一个传递数据的方法,解决了多重组件传值的问题。
-
使用Context的主要场景是:
- 需要在多层组件中共享数据
- 避免props逐层传递造成的繁琐
-
创建和使用Context的基本步骤:
- 使用React.createContext()创建一个Context对象
- 使用Context.Provider包裹父组件,并通过value属性提供要共享的数据
- 在子组件中使用Context.Consumer来消费Context中的数据
const MyContext = React.createContext(defaultValue);
<MyContext.Provider value={/* some value */}>
<ChildComponent />
</MyContext.Provider>
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
- 注意:
- Context的更新会触发使用该Context的组件重新渲染。
- 可以嵌套多个Provider来覆盖Context的值。
- 使用Context时需要注意性能问题,避免不必要的重渲染。