本文作者:IMWeb zixinfeng 原文出处:IMWeb社区 未经同意,禁止转载
1.React16.3的发布带来了一些新的特性, 除了新的ContextAPI之外, 还对生命周期做了部分修改, 为了支持未来的异步渲染特性, 一下生命周期将被废弃
2.废弃警告会在React16.4开启, 废弃的函数预计在React 17.0移除
1.App.jsx
state = {
name: 'fff',
age: 18
}
handleNameClick() {
this.setState({name: 'zzz'});
}
render() {
return (
<div>
<Button onClick={this.handleNameClick.bind(this)}>点击修改Name属性</Button>
<Children name={this.state.name} age={this.state.age}/>
</div>
)
}
2.Children.jsx
render() {
return (
<div}>
Name: {this.props.name}
Age: {this.props.age}
</div>
)
}
1.App.jsx
shouldComponentUpdate(nextProps,nextState) {
//如果更改之后的age和之前一样 返回false, 表示不重新渲染
if (nextState.age === this.state.age) {
return false;
}
return true;
}
onAgeChange() {
this.setState({age: 18});
}
render() {
return (
<div>
<Button onClick={this.handleNameClick.bind(this)}>点击修改Name属性</Button>
<Children name={this.state.name} age={this.state.age} onAgeChange={this.onAgeChange}/>
</div>
)
}
2.Children.jsx
render() {
return (
<div>
Name: {this.props.name}
Age: {this.props.age}
<button onClick={this.props.onAgeChange}>点击修改Age (not change age): {age}</button>
</div>
)
}
2.1 在Children组件中, 点击按钮, 调用父元素中的的onAgeChange函数, 但是在父元素中这里我们setState的修改后的age和修改之前prevState中age状态值是一样的,age都是18, 所以App的shouldComponentUpdate中返回false, 表示我们是不需要重新渲染的, 因为state中age并没有改变; 在上述情况下调用的生命周期如下
1.服务器端和客户端都只调用一次,在初始化渲染执行之前立刻调用. 如果在这个方法内调用 setState,render() 将会感知到更新后的 state,将会执行仅一次,尽管 state 改变了. 在服务端渲染时 componentDidMount 是不会被调用的,只会调用componentWillMount.
2.在componentWillMount中, 我们一般会用来异步获取数据, 但是在新版生命周期中, 官方不推荐我们这么做, 也不建议我们在constructor中, 有以下两点原因
3.官方推荐我们使用componentDidMount, 选择在componentDidMount有几个原因:
4.componentDidMount
// App.js
constructor(props) {
super(props)
this.state({
name: 'ccc'
})
}
componentDidMount() {
this.setState({name: 'fff'})
}
render() {
return (
<Child name={this.state.name} />
)
}
// Child.js
render() {
<div>
{this.props.name}
</div>
}
5.上面代码生命周期调用过程
1.在接收到新的 props 或者 state 之前立刻调用. 在初始化渲染的时候该方法不会被调用, 在render方法之前. 使用该方法做一些更新之前的准备工作, 例如读取当前某个 DOM 元素的状态并在componentDidUpdate中进行处理. 该生命周期有可能在一次更新中被调用多次, 也就是说写在这里的回调函数也有可能会被调用多次, 这显然是不可取的. 所以将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate 就可以解决这个问题 2.同时注意:你不能在componentWillUpdate方法中使用 this.setState(). 如果需要更新 state 来响应某个prop的改变, 请使用getDerivedStateFromProps
3.关于在组件更新之前读取DOM元素的状态
, React 提供了一个新的生命周期函数getSnapshotBeforeUpdate(prevProps, prevState), 具体我们可以看官方提供的例子
class ScrollingList extends React.Component {
listRef = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}
与componentWillUpdate 不同, getSnapshotBeforeUpdate 会在最终的 render 之前被调用, 也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的. 虽然 getSnapshotBeforeUpdate 不是一个静态方法, 但我们也应该尽量使用它去返回一个值. 这个值会随后被传入到 componentDidUpdate 中, 然后我们就可以在 componentDidUpdate 中去更新组件的状态, 而不是在 getSnapshotBeforeUpdate 中直接更新组件状态.
4.针对项目修改方案
1.在旧版的 React 中,如果组件自身的某个 state 跟其 props 密切相关的话,一直都没有一种很优雅的处理方式去更新 state,而是需要在 componentWillReceiveProps 中判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的 state 上去. 这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,也会增加组件的重绘次数.
// App.jsx
// 在其中添加上static getDerivedStateFromProps()
static getDerivedStateFromProps(extProps, prevState) {
}
// ChildComponent.jsx
constructor(props) {
super(props);
this.state = {
age: props.age
}
}
// Before
componentWillReceiveProps(nextProps) {
if (nextProps.age !== this.props.age) {
this.setState({
age: nextProps.age,
});
}
}
// After
static getDerivedStateFromProps(extProps, prevState) {
if (nextProps.age !== prevState.age) {
return {age}
}
}
2.旧版本的组件实力化的时候的生命周期
3.分别给App.jsx和Child.jsx中添加上static getDerivedStateFromProps方法后
2.简单分析static getDerivedStateFromProps 2.1 这个方法将会在组件实例化和接收到新的 props 的时候被调用. 而componentWillReceiveProps只会在接收到新的props的时候才会调用 2.1.1 当组件实例化的时候,这个方法替代了componentWillMount(),而当接收到新的 props 时,该方法替代了 componentWillReceiveProps() 和 componentWillUpdate().
2.1.2 需要注意,这个方法是个 static 的方法,因此使用 this 在这个方法中并不指代本实例组件,如果打印出来会发现这个this在这个方法中是null. 而且这个方法会返回值. 当需要更新状态时,需要返回一个 object ,如果不需要任何更新,则返回null即可.
2.1.3 如果由于父组件的原因导致该组件重新渲染,这个方法也会被调用, 如果只想要处理更新的话,最好加上判断条件if (nextProp !== prevProp). 另外,虽然this.setState()也会导致组件重新渲染,但并不会导致这个方法的重新调用.
state = {
name: 0
}
// old
componentWillReceiveProps(nextProps) {
if (nextProps.name !== this.props.name) {
if (this.state.loginStatus === null) {
fakeServerRequest(nextProps.name).then(result => {
// 实例属性 没有挂载在state中
this.loginStatus = result;
});
}
this.setState({
name: nextProps.name
})
}
}
// new
state = {
loginStatus: null,
name: 0
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.name !== prevState.name) {
return {
name: nextProps.name
loginStatus
}
}
return null;
}
// 将以前放在componenWillReceiveProps中的异步网络请求放在DidUpdate中
componentDidUpdate(nextProps) {
const { name } = nextProps;
if (this.state.loginStatus === null) {
fakeServerRequest(name).then(result => {
this.setState({ loginStatus: result });
});
}
}
1 context这个特性已经存在很久了,但因为一些原因一直是处于试验性质的API. React 16.3带来了正式版的context API
2.使用context
import React, { createContext } from 'react';
const ctx = createContext({
msg: 'hello world!',
});
const { Provider, Consumer } = ctx;
2.1 Provider组件用于将context数据传给该组件树下的所有组件 value属性是context的内容 2.2 要使用context的数据,我们需要使用Consumer组件
// 数据提供者
class App extends React.Component {
render() {
return (
<div>
<Provider value={{ msg: 'hello react!' }}>
<ChildComponent1 />
<ChildComponent2 />
</Provider>
<ChildComponent3 />
</div>
);
}
}
// 数据消费者
// 函数式
const ChildComponent1 = () => (
<Consumer>
{context => <p>{context.msg}</p>}
</Consumer>
);
// 类
class ChildComponent2 extends React.Component {
render() {
return (
<Consumer>
{context => <p>{context.msg}</p>}
</Consumer>
);
}
}
// 类
class ChildComponent3 extends React.Component {
render() {
return (
<Consumer>
{context => <p>{context.msg}</p>}
</Consumer>
);
}
}
/*
Consumer下不能写其它的东西,比如<Consumer>Message:{context => <p>{context.msg}</p>}</Consumer> 只能是一个函数 返回需要渲染的组件
*/
3.既然context的内容是写在Provider的value中,如果没有将Consumer作为Provider的子组件, 如上面的ChildComponent3,那么Consumer将使用创建context时的参数作为context
4.说一说新版 Context API 的几个特点: 4.1. Provider 和 Consumer 必须来自同一次 React.createContext 调用。也就是说 NameContext.Provider 和 AgeContext.Consumer 是无法搭配使用的。 4.2. React.createContext 方法接收一个默认值作为参数。当 Consumer 外层没有对应的 Provider 时就会使用该默认值。 4.3. Provider 组件的 value prop 值发生变更时,其内部组件树中对应的 Consumer 组件会接收到新值并重新执行 children 函数。此过程不受 shouldComponentUpdete 方法的影响。
// ParentComponent.jsx
state = {
msg: 'first'
}
changeState() {
this.setState({
msg: this.state.msg + '%-_-%'
})
}
render() {
return (
<div>
<button onClick={this.changeState.bind(this)}></button>
<ParentContext.Provider value={this.state.msg}>
<Children />
</ParentContext.Provider>
</div>
)
}
// ChildrenComponent.jsx
render() {
<ParentContext.Consumer>
<(msg)=> <p>{msg}</p>
</ParentContext.Consumer>
}
4.4.Provider 组件利用 Object.is 检测 value prop 的值是否有更新。注意 Object.is 和 === 的行为不完全相同。
4.5.Consumer 组件接收一个函数作为 children prop 并利用该函数的返回值生成组件树的模式被称为 Render Props 模式。