2.2.1:class 组件 之 state 和 props
2:React 组件
组件可被拆分为不同的功能片段,这些片段可以在其他组件中使用。组件可以返回其他组件、数组、字符串和数字。
React 组件允许将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
React 组件是可复用的小的代码片段,它们返回要在页面中渲染的 React 元素。
React 的组件可以定义为 class 或函数的形式。如下所示。 需要注意的是: 组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
2.1:React 之函数组件
React 组件的最简版本是,一个返回 React 元素的普通 JavaScript 函数,即函数形式:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
在函数组件中,外部数据是通过参数 props 传递的,但没有 state 和生命周期。React v16.8.0 推出了 Hooks API,其中的 useState 这个 API 可以解决了 state 问题,useEffect 解决了生命周期问题。函数组件中的 Hooks,使得目前的函数组件功能更加丰富。在后面的第 3 部分将详细介绍 React 的常用 Hooks。
2.1.1:函数组件 之 state 和 props
声明一个函数,它就是组件。可以用函数声明,也可以用函数表达式的方法。使用 useState 去读写 state,使用 props 去获取外部数据
function Welcome(props){
let [n,setn] = useState(10);//使用useState初始化了一个变量n,值为10。且使用setn修改n。
//也可以将n加10的操作写成一个函数
let addTen(){
setn(n+a10);
};
return <div>
<h1>Hello,{props.name}</h1>//使用参数props去接收外面的传参
<span>{n}</span}
<button onClick={setn(n+10)}>给n加10</button>//使用setn修改n的值
//使用addTen方法给n加10
<button onClick={addTen}>给n加10</button>//使用setn修改n的值
</div>
}
//使用方法:
<Welcome name="Jack"></Welcome>
2.1.2:函数组件 之 生命周期
使用 useEffect 去模拟 class 组件的生命周期
函数组件执行的时候,就相当于 constructor。shouldComponentUpdate 这个生命周期可以用后面的 React.memo 和 useMemo 可以解决。函数组件的返回值就是 render 的返回值。
1:模拟 componentDidMount
useEffect(()=>{console.log("第一次渲染")},[])
2:模拟 componentDidUpdate
useEffect(()=>{console.log("任意属性改变")}) useEffect(()=>{console.log("n 变了")},[n])
3:模拟 componentWillUnmount
useEffect(()=>{ console.log("第一次渲染"); return ()=>{ console.log("组件要死了"); }})
但现在有个问题,在 class 组件中使用 componentDidUpdate 这个钩子时,首次渲染是不会执行的,但是我们在函数组件中用 useEffect 去模拟该生命周期的时候,首次渲染是会执行的。这个问题怎么解决?可以自定义 Hooks,这个在第三部分详细讲解。
2.2:React 之 class 组件
也可以使用 ES6 的 class 编写,即 class 形式:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
如需定义 class 组件,需要继承 React.Component,且在 React.Component 的子类中有个必须定义的 render() 函数。
2.2.1:class 组件之 state 和 props
在 class 组件中,添加 state(内部数据),添加 props(外部数据),需要在 constructor 中进行初始化,如下图给组件 B 传递 props:
//声明了一个父组件Parent
class Parent extends React.component{
constructor(){
this.state = {name:'parent',n:100};
}//如果要初始化数据,即Parent组件里要有一个对象{name:'parent-component'},就要在costructor这里初始化
onClick2 = ()=>{this.setState((state)=>({n:state.n+1}))};
onclick3 = ()=>{this.state.n +=1;this.setState(this.state)}
onClick= ()=>{console.log("这里纯属测试")};
render(){
return (<div onClick={onClick2}>hi,this is {this.state.name}。
<Son name={this.state.name} onClick1={this.onClick} >hihihi</Son>
</div>);
}
}
//声明了一个子组件Son
class Son extends React.component{
constructor(props){
super(props);
this.state = {name:'son'};
}
render(){
return (<div onClick={this.props.onClick1}>hi,this is {this.state.name},my father is {this.props.name}
<div>{this.state.children}</div>
</div>);
}
}
在上面 Son 组件中,通过在 constructor 中 super(props)来初始化接收外部数据。通过 this.props.XXX 来读取。但是注意 props 由于是外部传进来的,因此理论上是不允许进行修改的。这需要自己写代码的时候规范。
在 constructor 中初始化 state,this.state 后面要跟对象。对 state 的读写,读用 this.state,写使用 this.setState(newState,fn),注意 setState 不会立刻改变 this.state,会在当前代码运行完后,再去更新 this.state,从而触发 UI 更新。this.setState((state,props)=>newState,fn)这种方式的 state 更容易理解,fn 会在写入成功后执行。
请注意 parent 组件中的 onClick2 和 onClick3 函数。一般是将一个新的 state 传递给 setState,但是如果将原来的 state 传进去,也是可以运行的。即 onClick3 也是可以的,但原则上并不提倡这样做。(在函数组件中,这样的话,React 会认为该对象没有变,是没有效果的,具体可以看下面函数组件的分析)
在 parent 组件中,state 里既有 name,又有 n,但是在设置点击事件改变 state 的时候,onClick2 函数中只传了 n,没有传 name,React 是会默认 shallow merge,自动将新 state 和旧 state 进行一级合并。(只会进行一级合并,但是在函数组件中,一级合并也不会,具体可以看下面函数组件的分析)
2.2.2:class 组件之 生命周期
生命周期钩子:所谓钩子其实就是一个函数,在特定的时间被调用。
挂载:当组件实例被创建并插入 DOM 中时,其主要的生命周期调用顺序如下:constructor()-->render()-->componentDidMount()。 注意:下述生命周期方法即将过时,在新代码中应该避免使用它们:UNSAFE_componentWillMount()
更新:当组件的 props 或 state 发生变化时会触发更新。组件更新的主要的生命周期调用顺序如下:shouldComponentUpdate()-->render()-->componentDidUpdate() 当 shouldComponent 这个生命周期钩子 return false 的时候,不会调用 render 和 componentDidUpdate,后续可能会有改变,详细的可去 React 官网查询。 注意:下述方法即将过时,在新代码中应该避免使用它们: UNSAFE_componentWillUpdate()UNSAFE_componentWillReceiveProps()
卸载:当组件从 DOM 中移除时会调用如下方法:componentWillUnmount()
常见的生命周期方法介绍:
1:constructor() 用途:初始化 props,初始化 state,但此处不能调用 setState,用来写 bind this
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
//还可以用新的语法:
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
}
this.handleClick = ()=> {};
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
2:render()
用途:展示视图。只能有一个根元素,如果有多个根元素的话,要用 React.Fragment 包起来,该标签可以缩写成<></>。render 里面可以写如 if-else,?:表达式,数组.map 循环等任意的 js 语法表达式。 render() 方法是 class 组件中唯一必须实现的方法。 当 shouldComponent 这个生命周期钩子 return false 的时候,不会调用 render。
3:componentDidMount()
用途:在元素插入页面后执行代码,这些代码依赖 DOM。比如如果想要获取元素的高度,最好在这里写。官方推荐在此处发起加载数据的 AJAX 请求。首次渲染会执行此钩子。这里也适合添加订阅,如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅。
4:componentWillReceiveProps() 当组件接收新的 props 时,会触发此钩子。该钩子已经被弃用,更名为 UNSAFE_componentWillReceiveProps。该钩子之前是推荐使用的,这里简单介绍一下。
5:ShouldComponentUpdate()和 pureComponent 组件
用途:根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。
首次渲染或使用 forceUpdate() 时不会调用该方法。此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。
你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
6:componentDidUpdate() 用途:在视图更新后执行代码。此处也可以发起 AJAX 请求,用于更新数据。首次渲染不会执行此钩子。在此处 setState 可能会引起无限循环,除非放在 if 里。若 shouldComponentUpdate 返回 false,则不触发此钩子。
7:componentWillUnmount()
用途:组件将要被移出页面然后被销毁时执行代码。unmount 过的组件不会再次 mount。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。