React V16
Opened this issue · 0 comments
React 16.3 新增的生命周期方法
getDerivedStateFromProps()
getSnapshotBeforeUpdate()
逐渐废弃的生命周期方法:
componentWillMount()
componentWillReceiveProps()
componentWillUpdate()
一般将生命周期分成三个阶段:
创建阶段(Mounting)
更新阶段(Updating)
卸载阶段(Unmounting)
从 React v16 开始,还对生命周期加入了错误处理(Error Handling)。
创建阶段 Mounting
组件实例创建并插入 DOM
时,按顺序调用以下方法:
- constructor()
- static getDerivedStateFromProps()
- componentWillMount()/UNSAFE_componentWillMount()(being deprecated)
- render()
- componentDidMount()
有定义 getDerivedStateFromProps
时,会忽略 componentWillMount()
/UNSAFE_componentWillMount()
constructor()
constructor(props)
构造函数通常用于:
- 使用 this.state 来初始化 state
- 给事件处理函数绑定 this
注意:ES6 子类的构造函数必须执行一次
super()
。React
如果构造函数中要使用this.props
,必须先执行super(props)
。
static getDerivedStateFromProps()
static getDerivedStateFromProps(nextProps, prevState)
当创建时、接收新的 props
时、setState
时、forceUpdate
时会执行这个方法。
注意:v16.3
setState
时、forceUpdate
时不会执行这个方法,v16.4 修复了这个问题。
这是一个 静态方法,参数 nextProps
是新接收的 props
,prevState
是当前的 state
。返回值(对象)将用于更新 state
,如果不需要更新则需要返回 null
。
下面是官方文档给出的例子
class ExampleComponent extends React.Component { // Initialize state in constructor, // Or with a property initializer. state = { isScrollingDown: false, lastRow: null, }; static getDerivedStateFromProps(props, state) { if (props.currentRow !== state.lastRow) { return { isScrollingDown: props.currentRow > state.lastRow, lastRow: props.currentRow, }; } // Return null to indicate no change to state. return null; }
这个方法的常用作用也很明显了:父组件传入新的 props
时,用来和当前的 state
对比,判断是否需要更新 state
。以前一般使用 componentWillReceiveProps
做这个操作。
这个方法在建议尽量少用,只在必要的场景中使用,一般使用场景如下:
- 无条件的根据
props
更新state
- 当
props
和state
的不匹配情况更新state
详情可以参考官方文档的最佳实践 You Probably Don’t Need Derived State
componentWillMount()
/UNSAFE_componentWillMount()
(弃用)
UNSAFE_componentWillMount()
这个方法已经不推荐使用。因为在未来异步渲染机制下,该方法可能会多次调用。它所行使的功能也可以由componentDidMount()
和 constructor()
代替:
- 之前有些人会把异步请求放在这个生命周期,其实大部分情况下都推荐把异步数据请求放在 componentDidMount() 中。
- 在服务端渲染时,通常使用 componentWillMount() 获取必要的同步数据,但是可以使用 constructor() 代替它。
可以使用 setState
,不会触发 re-render
render
rander()
每个类组件中,render()
唯一必须的方法。
render()
正如其名,作为渲染用,可以返回下面几种类型:
- React 元素(React elements)
- 数组(Arrays)
- 片段(fragments)
- 插槽(Portals)
- 字符串或数字(String and numbers)
- 布尔值或 null(Booleans or null)
注意:
- Arrays 和 String 是 v16.0.0 新增。
- fragments 是 v16.2.0 新增。
- Portals 是 V16.0.0 新增。
里面不应该包含副作用,应该作为纯函数。
不能使用 setState
。
componentDidMount()
componentDidMount()
组件完成装载(已经插入 DOM
树)时,触发该方法。这个阶段已经获取到真实的 DOM
。
一般用于下面的场景:
- 异步请求 ajax
- 添加事件绑定(注意在 componentWillUnmount 中取消,以免造成内存泄漏)
可以使用 setState
,触发re-render
,影响性能。
更新阶段 Updating
- componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()(being deprecated)
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- componentWillUpdate()/UNSAFE_componentWillUpdate()(being deprecated)
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
有 getDerivedStateFromProps
或者 getSnapshotBeforeUpdate
时,componentWillReceiveProps()
/UNSAFE_componentWillReceiveProps()
和 componentWillUpdate()
/UNSAFE_componentWillUpdate()
不会执行 (详情查看源码)
componentWillReceiveProps()
/UNSAFE_componentWillReceiveProps(
)(弃用)
UNSAFE_componentWillReceiveProps(nextProps)
这个方法在接收新的 props
时触发,即使 props
没有变化也会触发。
一般用这个方法来判断 props
的前后变化来更新 state
,如下面的例子:
class ExampleComponent extends React.Component { state = { isScrollingDown: false, }; componentWillReceiveProps(nextProps) { if (this.props.currentRow !== nextProps.currentRow) { this.setState({ isScrollingDown: nextProps.currentRow > this.props.currentRow, }); } } }
这个方法将被弃用,推荐使用 getDerivedStateFromProps
代替。
可以使用 setState
static getDerivedStateFromProps()
同 Mounting
时所述一致。
shouldComponentUpdate()
在接收新的 props
或新的 state
时,在渲染前会触发该方法。
该方法通过返回 true
或者 false
来确定是否需要触发新的渲染。返回 false
, 则不会触发后续的 UNSAFE_componentWillUpdate()
、render()
和 componentDidUpdate()
(但是 state
变化还是可能引起子组件重新渲染)。
所以通常通过这个方法对 props
和 state
做比较,从而避免一些不必要的渲染。
PureComponent
的原理就是对 props
和 state
进行浅对比(shallow comparison),来判断是否触发渲染。
componentWillUpdate()
/UNSAFE_componentWillUpdate()
(弃用)
UNSAFE_componentWillUpdate(nextProps, nextState)
当接收到新的 props
或 state
时,在渲染前执行该方法。
在以后异步渲染时,可能会出现某些组件暂缓更新,导致 componentWillUpdate
和 componentDidUpdate
之间的时间变长,这个过程中可能发生一些变化,比如用户行为导致 DOM
发生了新的变化,这时在 componentWillUpdate
获取的信息可能就不可靠了。
不能使用 setState
render()
同 Mounting
时所述一致。
getSnapshotBeforeUpdate()
getSnapShotBeforeUpdate(prevProps, prevState)
这个方法在 render()
之后,componentDidUpdate()
之前调用。
两个参数 prevProps
表示更新前的 props
,prevState
表示更新前的 state
。
返回值称为一个快照(snapshot)
,如果不需要 snapshot
,则必须显示的返回 null
—— 因为返回值将作为 componentDidUpdate()
的第三个参数使用。所以这个函数必须要配合componentDidUpdate()
一起使用。
这个函数的作用是在真实 DOM 更新(componentDidUpdate
)前,获取一些需要的信息(类似快照功能),然后作为参数传给 componentDidUpdate
。例如:在 getSnapShotBeforeUpdate
中获取滚动位置,然后作为参数传给 componentDidUpdate
,就可以直接在渲染真实的 DOM
时就滚动到需要的位置。
下面是官方文档给出的例子:
class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } 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) { const list = this.listRef.current; return list.scrollHeight - list.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) { const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; } } render() { return ({/* ...contents... */}); }
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
这个方法是在更新完成之后调用,第三个参数 snapshot
就是 getSnapshotBeforeUpdate
的返回值。
正如前面所说,有 getSnapshotBeforeUpdate
时,必须要有 componentDidUpdate
。所以这个方法的一个应用场景就是上面看到的例子,配合 getSnapshotBeforeUpdate
使用。
可以使用 setState
,会触发 re-render
,所以要注意判断,避免导致死循环。
卸载阶段 Unmounting
componentWillUnmount()
componentWillUnmount()
在组件卸载或者销毁前调用。这个方法主要用来做一些清理工作,例如:
- 取消定时器
- 取消事件绑定
- 取消网络请求
不能使用 setState
错误处理 Error Handling
React 16 引入了错误边界的概念。
错误边界是一种 React 组件,可以捕获子组件树中的 JavaScript 错误。它们会记录这些错误,并在错误 UI 上显示出来。错误边界会在渲染期间、生命周期方法中以及整个树的构造函数中捕获错误。
如果一个类组件定义了一个名为componentDidCatch(error,info)
的生命周期方法,那么它就会成为错误边界:
然后,你可以将其作为常规组件来使用。
componentDidCatch()
方法就像 JavaScript
的 catch{}
块,只是用在了组件上。只有类组件可以成为错误边界。实际上,大多数情况下,你每次只需要声明一个错误边界组件,然后就可以在整个应用程序中使用它。
任何子组件在渲染期间,生命周期方法中或者构造函数 constructor
发生错误时调用。
错误边界不会捕获下面的错误:
- 事件处理 (Event handlers) (因为事件处理不发生在 React 渲染时,报错不影响渲染)
- 异步代码 (Asynchronous code) (e.g. setTimeout or requestAnimationFrame callbacks)
- 服务端渲染 (Server side rendering)
- 错误边界本身(而不是子组件)抛出的错误
新的 render 返回类型:片段和字符串
在渲染时可以摆脱将组件包装在 div
中。
你现在可以从组件的 render
方法返回元素数组。与其他数组一样,你需要为每个元素添加一个键以避免发出键警告:
render() { // 不需要将清单项包装在额外的元素中! return [ // 不要忘了分配不同的键 :) <li key="A"> First item </li>, <li key="B"> Second item </li>, <li key="C"> Third item </li>, ]; }
从 16.2.0 版本开始,React 支持片段语法,不再需要键。
支持返回字符串:
render() { return 'Look ma, no spans!'; }
Portal
Portal
提供了一种将子组件渲染为存在于父组件的 DOM
层次结构之外的 DOM
节点的方法。
ReactDOM.createPortal(child, container)
第一个参数(child
)是可渲染的 React
子节点,如元素、字符串或片段。第二个参数(container
)是一个 DOM
元素。
在从组件的 render
方法返回一个元素时,它将作为最近父节点的子节点挂载到 DOM
中:
render() { // React 加载一个新 div,并在其中渲染子组件 return ( <div> {this.props.children} </div> ); }
有时候需要将子组件插入到 DOM
的其他位置:
render() { // React 不创建一个新 div。它将子组件渲染到`domNode`中。 // `domNode` 是一个合法的 DOM 节点,不管它在 DOM 中处于什么位置。 return ReactDOM.createPortal( this.props.children, domNode ); }
Portal
的一个典型用例是这样的:当父组件带有 overflow:hidden
或 z-index
样式时,你希望子组件在视觉上能够“突破”它的容器。例如,对话框、悬停卡和工具提示。
自定义 DOM 属性
React15 会忽略任何未知的 DOM 属性。React 会跳过它们,因为无法识别它们。
// 你的代码: <div mycustomattribute="something" />
React 15 将渲染一个空的 div:
// React 15 的输出: <div />
React16 的输出如下(自定义属性会被显示出来,根本不会被忽略):
// React 16 的输出: <div mycustomattribute="something" />
在 state 中设置 null 避免重新渲染
在 React16 中,你可以直接在 setState()
中防止 state
更新和重新渲染。你只需要让你的函数返回 null
。
const MAX_PIZZAS = 20; function addAnotherPizza(state, props) { // 如果我有足够的披萨,就停止更新和重新渲染。 if (state.pizza === MAX_PIZZAS) { return null; } // 否则的话,让披萨来得更猛烈些吧:D return { pizza: state.pizza + 1, } } this.setState(addAnotherPizza);
创建 ref
在 React16 中创建 ref
要容易得多。为什么需要使用 ref
:
- 管理焦点、文本选择或媒体播放。
- 触发动画。
- 与第三方 DOM 库集成。
ref
是使用 React.createRef()
创建的,并通过 ref
属性附加到 React
元素。ref
通常是在构造组件时被分配给实例的属性,以便在整个组件中引用它们。
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }
ref
被传给元素后,可以通过 ref
的 current
属性访问对节点的引用。
const node = this.myRef.current;
ref
的值因节点的类型不同而有所不同:
当 ref
属性用于 HTML
元素时,在构造函数中使用 React.createRef()
创建的 ref
将底层 DOM
元素作为 current
属性。当 ref
属性用于自定义类组件时,ref
对象将已挂载的组件实例作为 current
属性。你可能不会在功能组件上使用 ref
属性,因为它们没有实例。
Context API
Context
提供了一种通过组件树传递数据的方法,无需在每一层手动传递 prop
。
React.createContext
const {Provider, Consumer} = React.createContext(defaultValue);
创建{Provider,Consumer}
对。当 React
渲染 Consumer
时,它将从树中最接近的 Provider
读取当前上下文值。
defaultValue
参数只在消费者在树中找不到匹配的 Provider
时才会用到,这在单独测试组件时十分有用。注意:将 undefined
作为 Provider
值传递进去并不会导致 Consumer
使用 defaultValue
。
Provider
<Provider value={/* 一些值 */}>
一个允许 Consumer
订阅上下文变更的 React
组件。
一个 Provider
可以连接多个 Consumer
,可以在树中嵌套 Provider
,实现更深的值覆盖。
Consumer
<Consumer> {value => /* 基于上下文值渲染一些东西 */} </Consumer>
一个订阅上下文变更的 React
组件。
需要一个函数作为子组件。这个函数接收当前上下文值,并返回一个 React
节点。传给函数的 value
参数将等于树中最近的 Provider
的 value
。如果没有匹配的 Provider
,则 value
参数将等于传给 createContext()
的 defaultValue
。
静态 getDerivedStateFromProps()
getDerivedStateFromProps
会在调用 render
方法之前被调用,它应该返回一个用于更新状态的对象,或者如果不更新任何状态就返回 null
。
这个方法适用于一些罕见的用例,其中 state
依赖 prop
的变化。例如,可以很方便地实现一个组件,它会比较上一个和下一个子组件,然后决定它们中的哪个需要进行动画渲染。
衍生 state 会导致冗长的代码,并让你的组件难以开发和维护。
你可以考虑更简单的替代方案:
如果你需要在 prop
发生变更时做一些其他事情(例如数据提取或动画),请改用 componentDidUpdate
。如果你只想在 prop
发生变更时重新计算某些数据,请改用备忘辅助器:
如果你想在 prop
发生变更时“重置”某个状态,请考虑创建完全控制组件或带有键的完全不受控制组件。
这个方法无法访问到组件实例。如果你愿意,可以在类定义之外提取组件 prop
和 state
的纯函数,在 getDerivedStateFromProps()
和其他类方法之间重用一些代码。
请注意,不管怎样,这个方法都会在每次进行渲染时触发。这与 UNSAFE_componentWillReceiveProps
完全相反。它只在父组件进行重新渲染时触发,而且不作为本地 setState
的结果。
我们将 nextProps.someValue
与 this.props.someValue
进行对比。如果它们的值不一样,我们就执行一些操作:
static getDerivedStateFromProps(nextProps, prevState){ if(nextProps.someValue!==prevState.someValue){ return { someState: nextProps.someValue}; } else return null;}
它接收两个参数 nextProps
和 prevState
。如前所述,你无法在这个方法中访问 this
。你必须将 prop
存储在 state
中,然后将 nextProps
与之前的 prop 进行对比。在上面的代码中,nextProps
和 prevState
进行了比较。如果两者不同,则返回一个用于更新状态的对象,否则就返回 null
,表示不需要更新状态。如果 state
发生变更,就会调用 componentDidUpdate
,我们可以像在 componentWillReceiveProps
中那样执行所需的操作。