React学习 —— 受控组件、生命周期、PureCompone
受控组件与非受控组件
受控组件:
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()
来更新。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
该组件实现了一个点击提交,打印名字的功能。
有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数(因为数据都存在state里,所以需要编写事件处理函数更新state,就比如这里都handleChange),并通过一个 React 组件传递所有的输入 state。
非受控组件
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
这里和受控组件都区别就在于,我们并没有给input赋值(value={this.state.xxx}),数据本身都是存储在dom节点本身的,每次我们需要获取或者改变的时候我们应该直接去通过ref操作dom节点,而非设置state。
默认值
如果是受控组件,value就可以充当默认值,而非受控组件由于没有可以传递的地方本应该是除了直接操作dom之外没有设置默认值的方法的,但是react提供了封装,我们可以在dom上添加default值:
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
生命周期
常用的生命周期方法
本节中的方法涵盖了创建 React 组件时能遇到的绝大多数用例。想要更好了解这些方法,可以参考生命周期图谱。
render()
只能返回:React、fragments、Portals、字符串或者数值、布尔类型或 null。
constructor()
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部 state。 - 为事件处理函数绑定实例
如果不做这些处理,则不需要constructor()
避免将 props 的值复制给 state!这是一个常见的错误!!
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。
这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅。
你可以在 componentDidMount() 里可以直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。
componentWillUnmount()
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
shouldComponentUpdate()
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。
我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。
其他API
setState()
setState(updater[, callback])
将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。
setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。如需基于之前的 state 来设置当前的 state,请阅读下述关于参数 updater 的内容。
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
updater 函数中接收的 state 和 props 都保证为最新。updater 的返回值会与 state 进行浅合并。
有关更多详细信息,请参阅:
forceUpdate()
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。
调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。
ReactDOM
react-dom
的 package 提供了可在应用顶层使用的 DOM(DOM-specific)方法,如果有需要,你可以把这些方法用于 React 模型以外的地方。不过一般情况下,大部分组件都不需要使用这个模块。
React.Component与React.PureComponent
React.PureComponent
与 React.Component
很相似。两者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
shouldComponentUpdate在刚刚的生命周期中也说过,当props或者state发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
返回默认值true,首次渲染和 forceUpdate() 时不会调用该方法。
如果 shouldComponentUpdate()
返回 false
,则不会调用 UNSAFE_componentWillUpdate()
,render()
和 componentDidUpdate()
。
而React.Component的shouldComponentUpdate默认就是返回true的,React.PureComponent是实现一套逻辑,浅比较props和state,并减少了跳过必要更新的可能性。
什么是浅比较?
对于基本类型(primitives),例如数字或者布尔值,来说,浅拷贝将会检查其值是否相同,例如1与1相等,true与true相等。对于引用类型的变量,例如复杂的javascript对象或者数组,来说,浅拷贝将仅仅检查它们的引用值是否相等。这意味着,对于引用类型的变量来说,如果我们只是更新了其中的一个元素,例如更新了数组中某一位置的值,那么更新前后的数组仍是相等的。
因此意味着相比于Component,PureCompoent的性能表现将会更好。但使用PureCompoent要求满足如下条件:
- props和state都不可变
- props和state没有层级
- 如果数据改变无法反应在浅拷贝上,应该调用forceUpdate更新。
React.PureComponent
中的 shouldComponentUpdate()
仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 props 和 state 较为简单时,才使用 React.PureComponent
,或者在深层数据结构发生变化时调用 forceUpdate()
来确保组件被正确地更新。