getDerivedStateFromProps详解

首先看一下新旧生命周期的对比

React旧生命周期示意图
React新生命周期示意图

  • 目的:为 async rendering 和 fiber 调度服务的,确保渲染完成之前不做任何引发重新渲染的操作
  • async rendering:react将来会采用异步渲染机制
1
2
3
4
5
6
7
8
9
10
11
12
function setState(state, callback) {
let s = this.state;
if (!this.prevState) this.prevState = extend({}, s);
extend(s, typeof state==='function' ? state(s, this.props) : state);
if (callback) this._renderCallbacks.push(callback);
enqueueRender(this);
}
function enqueueRender(component) {
if (!component._dirty && (component._dirty = true) && items.push(component)==1) {
(options.debounceRendering || defer)(rerender); // 异步呀
}
}
  • fiber => 节点,涉及到react渲染原理和diff算法

getDerivedStateFromProps的几个点

  1. react16.3版本引入
  2. 替换componentWillMount componentWillReceiveProps周期,从上面新旧周期函数对比就能看出
  3. 参数:nextProps prevState,不引入prevProps是为了节省性能
  4. 目的:使组件能够根据props的变化更新其内部状态,变得更为安全
  5. 返回值问题:
    1. 返回object对象,则相当于进行了一次setState操作,这里返回对象虽然改变了state,但不会再次出发该函数;
    2. 返回null,则不更新state
    3. 不返回值,则默认返回undefined,会报错
    4. 返回基本类型值,则与返回null是一样的结果
  6. 不能与17.x版本即将删除的三个生命周期函数调用

与componentWillReceiveProps的比较

  1. 不会产生副作用,以往我会在componentWillReceiveProps里更新state,并执行ajax异步操作,官方认为这样不够优雅,职能不专一,且渲染过程会被打断,所以新版中,将副作用放在componentDidMount和componentDidUpdate中执行
  2. 执行时机的不同:getDerivedStateFromProps会在组件实例化和接收到新的props的时候都会被调用
  3. static 静态方法,拿不到this及实例的属性和方法
  4. 贴个项目里用的demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
state={loading: false};
componentDidMount(){
this.queryUserInfo();
};
static getDerivedStateFromProps(nextProps, prevState) {
const { pathname, search } = nextProps.location;
if(!isEqual(pathname, prevState.prevPathname)){
return{
pathname,
prevPathname: pathname,
search: parse(search, { ignoreQueryPrefix: true}),
}
};
return null;
};

componentDidUpdate(prevProps,prevState){
const path = this.state.pathname;
if (!isEqual(path, prevState.pathname)){
this.queryUserInfo()
}
}

getDerivedStateFromProps使用中存在的问题

  1. props和state的变化都会触发getDerivedStateFromProps函数的执行,会引发多次渲染
  2. 子组件接收父组件的属性,但又要通过自身setState去更新这个属性时,会造成无法更新子组件的属性
    • 不要无条件的去更新state,这点尤为重要

在hooks中实现getDerivedStateFromProps函数

1
2
3
4
5
6
7
8
9
10
11
12
function ScrollView({row}) {
const [isScrollingDown, setISScrollingDown] = setState(false)
const [prevRow, setPrevRow] = setState(null)

// 核心是创建一个 prevRow state 与父组件传进来的 row 进行比较
if (row !== prevRow) {
setISScrollingDown(prevRow !== null && row > prevRow)
setPrevRow(row)
}

return `Scrolling down ${isScrollingDown}`
}

我们一定使用getDerivedStateFromProps这个周期函数

  • 答案:不是的,甚至react官方将其称之为反模式,违反设计语言,不推荐使用
  • react官方推荐,完全受控组件,及子组件的状态完全受控于父组件,即使你子组件试图更改传过来的状态,也通过替他方式
  • 下面贴图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// PureComponents只会在至少state和props中有一个属性发生变化时渲染.
// 变化是通过引用比较来判断的.
class Example extends PureComponent {
// State only needs to hold the current filter text value:
state = {
filterText: ""
};

handleChange = event => {
this.setState({ filterText: event.target.value });
};

render() {
// 只有props.list 或 state.filterText 改变时才会调用.
const filteredList = this.props.list.filter(
item => item.text.includes(this.state.filterText)
)

return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

import memoize from "memoize-one";

class Example extends Component {
// State只需要去维护目前的筛选关键字:
state = { filterText: "" };

// 当列表数组或关键字变化时重新筛选
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);

handleChange = event => {
this.setState({ filterText: event.target.value });
};

render() {
// 计算渲染列表时,如果参数同上次计算没有改变,`memoize-one`会复用上次返回的结果
const filteredList = this.filter(this.props.list, this.state.filterText);

return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
-------------本文结束感谢您的阅读-------------