[TOC]javascript
新的生命周期函数 getDerivedStateFromProps 位于 Mounting 挂载阶段和由 props 更新触发的 Updating 阶段。getDerivedStateFromProps 主要用于替换 componentWillReceiveProps 函数,其功能更加明确,就是根据 props 更新组件的 state。html
class Demo extends React.Component{
state = {
tmpA: 1,
tmpB: 'test',
lastA: null,
lastB: null
};
static getDerivedStateFromProps(props, state){
if(props.a !== state.lastA){
return {
tmpA: props.a,
lastA: props.a
}
}
return null;
}
render(){
return <div> <input value={this.state.tmpA} onChange={e => { this.setState({tmpA: e.target.value}) }} /> </div> } } 复制代码
须要注意的几点是:java
getSnapshotBeforeUpdate 函数调用于 render 函数以后 componentDidUpdate 函数以前,主要用于获取更新前的 DOM 元素的信息。关于该函数的用法,React 的官方示例为:react
```javascript
//这是一个带滚动条的列表组件
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//若是这次更新中,列表变长,则记录更新前滚动的位置
//并做为componentDidUpdate函数的第三个参数传入
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
//默认返回null
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
//根据更新前的滚动位置,设置更新后列表的滚动长度
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
```
复制代码
须要注意的几点:设计模式
能够看出 React16 中,类组件的一个组件的生命周期被划分为类 render 和 commit 两个阶段,render 阶段主要负责组件渲染相关,包括对渲染数据 state 的更新。为了防止一次更新中 render 阶段重复执行,React 将该阶段可能引入 side effects 的生命周期函数 componentWillReceiveProps、componentWillUpdate、componentWillUnmount 等函数移除。
针对须要经过 props 计算 derived state 的需求,提供静态函数 getDerivedStateFromProps。针对获取更新前 DOM 元素的需求,React16 提供了 getSnapshotBeforeUpdate 生命周期函数。数组
React 官方文档对 Hooks 的介绍是 Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class,从介绍中咱们可知:服务器
组件逻辑代码复用 相比于 HOC、renderProps 而言,须要提供一个更加灵活轻便而且是原生支持的状态逻辑复用机制。我的总结 renderProps 和 HOC 的缺点在于:app
HOC、render props 和 Hooks 实现逻辑代码复用示例
当咱们须要实现一个简单的显示隐藏的功能时,通常是在组件的 state 中定义一个控制现实仍是隐藏的布尔变量,并再添加一个 show 和 hide 方法来控制该变量。若是要将这段逻辑代码提取出来复用的话,能够经过高阶组件 HOC、render props 或者 Hooks 来实现,如下分别列出这三种方法的实现代码以及对应的 React 组件树结构。能够看出使用 Hooks 复用逻辑代码时,因为没有建立额外的组件,故不管是代码仍是最后生成的 React 组件树,都是 Hooks 的实现方式更简洁。dom
const Wrapper = WrappedComponent => class extends React.Component{
constructor(props) {
super(props);
this.state = {
isDisplayed: defaultTo(props.initialState, false),
};
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hide() {
this.setState({isDisplayed: false,});
}
show() {
this.setState({isDisplayed: true,});
}
render(){
const newProps = {
...this.props,
...this.state,
hide: this.hide,
show: this.show
};
return <WrappedComponent {...newProps}/> } }; const App = Wrapper(({isDisplayed, hide, show}) => { return ( <div className="App"> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div> ); }) 复制代码
class VisibilityHelper extends React.Component {
constructor(props) {
super(props);
this.state = {
isDisplayed: defaultTo(props.initialState, false),
};
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hide() {
this.setState({isDisplayed: false});
}
show() {
this.setState({isDisplayed: true});
}
render() {
return this.props.children({
...this.state,
hide: this.hide,
show: this.show,
});
}
}
const App = () => (
<div className="App"> <VisibilityHelper> { ({isDisplayed, hide, show}) => ( <div> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div> ) } </VisibilityHelper> </div>
);
复制代码
import React, {useState} from 'react';
function useIsDisplayed(initialValue) {
const [isDisplayed, setDisplayed] = useState(defaultTo(initialValue, false));
function show(){
setDisplayed(true);
}
function hide() {
setDisplayed(false);
}
return {isDisplayed, show, hide};
}
const App = () => {
const {isDisplayed, show, hide} = useIsDisplayed(false);
return (
<div className="App"> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div>
);
};
复制代码
武装函数式组件 React 如今力推函数式组件,并逐渐弃用 class 组件(具体缘由后面章节再讨论)。以前的函数式组件因为没有 state 以及类型 class 组件生命周期函数的机制,每每用来做展现型组件。而经过 useState hook 能够为函数式组件添加 state,经过 useEffect 能够在函数式组件中实现 class 组件生命周期函数的功能。异步
useState 函数用于给函数式组件提供可以持久存储并能将变化映射到视图上的状态 state hook,相似 class 组件中的 state。在上节给出的 Hooks 实现逻辑代码复用的例子中已经展现了 useState 函数的使用。useState 函数的返回值为一个数组,数组中第一个元素为 state 对象,第二个元素为一个 dispatch 方法,用于更新该 state。基本使用为:
import React, {useState} from 'react';
function Counter(){
//声明一个 count 变量,能够经过 setCount dispatch 一个新的 count 值
const [count, setCount] = useState(0);
useState('the second state');
useState('the third state');
return <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+</button> </div>
}
复制代码
useEffect 函数用于建立一个 effect hook。effect hooks 用于在函数式组件的更新后执行一些 side effects,其之于函数式组件就至关于 componentDidMount 和 componentDidUpdate 之于 class 组件。useEffect 的基本用法为:
const App = () => {
const [text, setText] = useState('');
useEffect(() => {
window.addEventListener('keydown', writing, false);
return () => {
window.removeEventListener('keydown', writing, false);
}
});
function writing(e){
setText(text + e.key);
}
return (
<div className="App"> <p>{text}</p> </div>
);
};
复制代码
上例是一个使用函数式组件和 state hooks 以及 effect hooks 实现的监听用户键盘输入并现实输入字符串的功能。能够看到,咱们使用 state hooks 来存储以及更新用户输入的字符串,用 effect hooks 来监听以及取消监听 keydown 事件。
window.removeEventListener
再执行 window.addEventListener
useEffect(() => {}, [])
。当传入第二个参数时,只有当第二个参数中的某个 state 或者 props 改变时,该 effect hook 才会被调用。值得注意的是:此时 effect hook 以及在其间的回调函数只能访问到 useEffect 数组参数中的 state 和 props 的最新值,其它 state 和 props 只能获取到初始值和 React16 中提供的 createRef 方法同样,用于获取 React 组件的 ref。官方示例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
//inputEl 的 current 属性指向 input 组件的 dom 节点
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
复制代码
除了用于获取组件 ref 之外,useRef 还能够用于实现相似于 class 组件的实例属性(this.XXX),直接经过对 useRef 方法返回对象的 current 属性进行读写便可。
useReducer 实现了 Redux 中的 reducer 功能。当 state 的逻辑比较复杂的时候,能够考虑使用 useReducer 来定义一个 state hook。示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 复制代码
上例是 React 官方提供的 useReducer 的使用示例。固然和 Redux 同样,dispatch 的 action 能够由 actionCreator 来生成。
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
本节直接使用 React 官网提供的示例代码,须要注意的是,自定义 Hooks 也须要知足上节中提到的使用规则。
import React, { useState, useEffect } from 'react';
//useFriendStatus 为根据好友 ID 获取好友是否在线的自定义 Hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
//聊天对象选择列表组件
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
//经过使用 useFriendStatus Hook,
//将获取当前好友是否在线的逻辑从组件中分离出去
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
复制代码
function F({count = 0, isPure = false}){
console.log(`render ${isPure ? 'pure FC' : 'FC'} ${count} times`);
return <h2>{count}</h2>
}
const PureFC = React.memo(F, /*areEqual(prevProps, nextProps)*/);
class App extends React.Component{
state = {
count: 0,
fakeState: false
};
render(){
const {count, fakeState} = this.state;
return <div>
<F count={count} isPure={false}/>
<PureFC count={count} isPure={true}/>
<button onClick={() => {
this.setState({count: count + 1})}
}>increase</button>
<button onClick={() => {this.setState({fakeState: !fakeState})}}>Click2</button>
</div>;
}
}
//*click increase button
//render FC 1 times
//render pure FC 1 times
//*click Click2 button
//render FC 1 times
复制代码
上例说明,即便 FC(函数式组件) 的 props 没有变化,当父组件更新时,仍是会从新渲染 FC。但用 React.memo 高阶组件包裹的 FC 却能够跳过 props 没有变化的更新。为了支持更加灵活的 props 对比,React.memo 还支持传入第二个函数参数 areEqual(prevProps, nextProps)。该函数返回 true 时不更新所包裹的 FC,反之更新 FC,这点与 shouldComponentUpdate 函数相反。
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<> {child1} {child2} </> ) } 复制代码
Context 主要解决了 React 组件树非父子组件的状态共享问题,以及子组件与祖先组件以前多层 props 传递繁琐的问题。官方示例:
const ThemeContext = React.createContext('light');
class ThemeProvider extends React.Component {
state = {theme: 'light'};
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
const ThemedButton = () => {
render() {
return (
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
复制代码
###Error Boundary React16 中若是组件生命周期中抛出了未经捕获的异常,会致使整个组件树卸载。React16 提供了两个生命周期函数用于捕获子组件中在生命周期中抛出的异常。一个是 static getDerivedStateFromError(error) 在渲染阶段 render 函数前调用,,另外一个是 componentDidCatch 在 commit 阶段即完成渲染后调用。关于这两个函数,React 官方示例为:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
//主要用于根据额 error 更新 state.
//和 getDerivedStateFromProps 函数相似,
//返回的对象会更新到 state
return { hasError: true };
}
componentDidCatch(error, info) {
//主要用于在 commit 阶段处理错误相关的 side effects
//好比此处的发送错误信息到服务器
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
复制代码
关于 Error Boundary 须要注意的几点是:
在使用 React16 时,若是咱们在渲染组件时须要渲染一个脱离于当前组件树以外的组件(如对话框、tooltip等),能够经过 ReactDOM.createPortal(Child, mountDom)* 函数建立一个 Portal,将 React 组件 Child 挂载到真实 DOM 元素 mountDom 上。示例代码:
//html <body> <div id="root"></div> <div id="modal"></div> </body> //js const modalDom = document.querySelector('#modal'); function Child(){ function handleClick(){ console.log('click child'); } return <button onClick={handleClick}>Child Button</button> } function App(){ const [count, setCount] = useState(0); return <div onClick={() => {setCount(count + 1)}}> <h1>{count}</h1> { ReactDOM.createPortal( <Child/>, modalDom //将 Child 挂载到 id=modal 的 div 元素下 ) } </div> } //将 App 挂载到 id=root 的 div 元素下 ReactDOM.render(<App />, document.getElementById('root')); 复制代码
上例中,虽然 Child 组件的真实 DOM 节点挂载在 modal 下,而 App 组件的真实 DOM 节点挂载在 root 下。但 Child 组件中的 Click 事件仍然会冒泡到 App 组件。故咱们点击 button 时,会依次触发 Child 组件的 handleClick 函数,以及 App 组件的 setCount 操做。
React16 中能够经过 React.Fragment 组件来组合一列组件,而不须要为了返回一列组件专门引入一个 DIV 组件。其中 <></>
是 <React.Fragment></React.Fragment>
的简写。