直播回放连接: 云栖社区 ( @x-cold)
Hooks 顾名思义,字面意义上来讲就是 React 钩子的概念。经过一个 case 咱们对 React Hooks 先有一个第一印象。javascript
假设如今要实现一个计数器的组件。若是使用组件化的方式,咱们须要作的事情相对更多一些,好比说声明 state,编写计数器的方法等,并且须要理解的概念可能更多一些,好比 Javascript 的类的概念,this 上下文的指向等。html
示例java
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; class Counter extends React.Component { state = { count: 0 } countUp = () => { const { count } = this.state; this.setState({ count: count + 1 }); } countDown = () => { const { count } = this.state; this.setState({ count: count - 1 }); } render() { const { count } = this.state; return ( <div> <button onClick={this.countUp}>+</button> <h1>{count}</h1> <button onClick={this.countDown}>-</button> </div> ) } } ReactDOM.render(<Counter />, document.getElementById('root'));
使用 React Hooks,咱们能够这么写。react
示例git
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; function Counter() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <h1>{count}</h1> <button onClick={() => setCount(count - 1)}>-</button> </div> ) } ReactDOM.render(<Counter />, document.getElementById('root'));
经过上面的例子,显而易见的是 React Hooks 提供了一种简洁的、函数式(FP)的程序风格,经过纯函数组件和可控的数据流来实现状态到 UI 的交互(MVVM)。github
Additional Hooksredux
useState 是最基本的 API,它传入一个初始值,每次函数执行都能拿到新值。数组
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; function Counter() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>+</button> <h1>{count}</h1> <button onClick={() => setCount(count - 1)}>-</button> </div> ) } ReactDOM.render(<Counter />, document.getElementById('root'));
须要注意的是,经过 useState 获得的状态 count,在 Counter 组件中的表现为一个常量,每一次经过 setCount 进行修改后,又从新经过 useState 获取到一个新的常量。app
useReducer 和 useState 几乎是同样的,须要外置外置 reducer (全局),经过这种方式能够对多个状态同时进行控制。仔细端详起来,其实跟 redux 中的数据流的概念很是接近。
import { useState, useReducer } from 'react'; import ReactDOM from 'react-dom'; function reducer(state, action) { switch (action.type) { case 'up': return { count: state.count + 1 }; case 'down': return { count: state.count - 1 }; } } function Counter() { const [state, dispatch] = useReducer(reducer, { count: 1 }) return ( <div> {state.count} <button onClick={() => dispatch({ type: 'up' })}>+</button> <button onClick={() => dispatch({ type: 'down' })}>+</button> </div> ); } ReactDOM.render(<Counter />, document.getElementById('root'));
一个相当重要的 Hooks API,顾名思义,useEffect 是用于处理各类状态变化形成的反作用,也就是说只有在特定的时刻,才会执行的逻辑。
import { useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; function Example() { const [count, setCount] = useState(0); // => componentDidMount/componentDidUpdate useEffect(() => { // update document.title = `You clicked ${count} times`; // => componentWillUnMount return function cleanup() { document.title = 'app'; } }, [count]); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ReactDOM.render(<Example />, document.getElementById('root'));
useMemo 主要用于渲染过程优化,两个参数依次是计算函数(一般是组件函数)和依赖状态列表,当依赖的状态发生改变时,才会触发计算函数的执行。若是没有指定依赖,则每一次渲染过程都会执行该计算函数。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import { useState, useMemo } from 'react'; import ReactDOM from 'react-dom'; function Time() { return <p>{Date.now()}</p>; } function Counter() { const [count, setCount] = useState(0); const memoizedChildComponent = useMemo((count) => { return <Time />; }, [count]); return ( <div> <h1>{count}</h1> <button onClick={() => setCount(count + 1)}>+</button> <div>{memoizedChildComponent}</div> </div> ); } ReactDOM.render(<Counter />, document.getElementById('root'));
context 是在外部 create ,内部 use 的 state,它和全局变量的区别在于,若是多个组件同时 useContext,那么这些组件都会 rerender,若是多个组件同时 useState 同一个全局变量,则只有触发 setState 的当前组件 rerender。
import { useState, useContext, createContext } from 'react'; import ReactDOM from 'react-dom'; // 1. 使用 createContext 建立上下文 const UserContext = new createContext(); // 2. 建立 Provider const UserProvider = props => { let [username, handleChangeUsername] = useState(''); return ( <UserContext.Provider value={{ username, handleChangeUsername }}> {props.children} </UserContext.Provider> ); }; // 3. 建立 Consumer const UserConsumer = UserContext.Consumer; // 4. 使用 Consumer 包裹组件 const Pannel = () => ( <UserConsumer> {({ username, handleChangeUsername }) => ( <div> <div>user: {username}</div> <input onChange={e => handleChangeUsername(e.target.value)} /> </div> )} </UserConsumer> ); const Form = () => <Pannel />; const App = () => ( <div> <UserProvider> <Form /> </UserProvider> </div> ); ReactDOM.render(<App />, document.getElementById('root'));
import { useState, useContext, createContext } from 'react'; import ReactDOM from 'react-dom'; // 1. 使用 createContext 建立上下文 const UserContext = new createContext(); // 2. 建立 Provider const UserProvider = props => { let [username, handleChangeUsername] = useState(''); return ( <UserContext.Provider value={{ username, handleChangeUsername }}> {props.children} </UserContext.Provider> ); }; const Pannel = () => { const { username, handleChangeUsername } = useContext(UserContext); // 3. 使用 Context return ( <div> <div>user: {username}</div> <input onChange={e => handleChangeUsername(e.target.value)} /> </div> ); }; const Form = () => <Pannel />; const App = () => ( <div> <UserProvider> <Form /> </UserProvider> </div> ); ReactDOM.render(<App />, document.getElementById('root'));
useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。事实上 useRef 是一个很是有用的 API,许多状况下,咱们须要保存一些改变的东西,它会派上大用场的。
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
说到状态共享,最简单和直接的方式就是经过 props 逐级进行状态的传递,这种方式耦合于组件的父子关系,一旦组件嵌套结构发生变化,就须要从新编写代码,维护成本很是昂贵。随着时间的推移,官方推出了各类方案来解决状态共享和代码复用的问题。
React 中,只有经过 createClass 建立的组件才能使用 mixins。这种高耦合,依赖难以控制,复杂度高的方式随着 ES6 的浪潮逐渐淡出了历史舞台。
高阶组件源于函数式编程,因为 React 中的组件也能够视为函数(类),所以天生就能够经过 HOC 的方式来实现代码复用。能够经过属性代理和反向继承来实现,HOC 能够很方便的操控渲染的结果,也能够对组件的 props / state 进行操做,从而能够很方便的进行复杂的代码逻辑复用。
import React from 'react'; import PropTypes from 'prop-types'; // 属性代理 class Show extends React.Component { static propTypes = { children: PropTypes.element, visible: PropTypes.bool, }; render() { const { visible, children } = this.props; return visible ? children : null; } } // 反向继承 function Show2(WrappedComponent) { return class extends WrappedComponent { render() { if (this.props.visible === false) { return null; } else { return super.render(); } } } } function App() { return ( <Show visible={Math.random() > 0.5}>hello</Show> ); }
Redux 中的状态复用是一种典型的 HOC 的实现,咱们能够经过 compose 来将数据组装到目标组件中,固然你也能够经过装饰器的方式进行处理。
import React from 'react'; import { connect } from 'react-redux'; // use decorator @connect(state => ({ name: state.user.name })) class App extends React.Component{ render() { return <div>hello, {this.props.name}</div> } } // use compose connect((state) => ({ name: state.user.name }))(App);
显而易见,renderProps 就是一种将 render 方法做为 props 传递到子组件的方案,相比 HOC 的方案,renderProps 能够保护原有的组件层次结构。
import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; // 与 HOC 不一样,咱们可使用具备 render prop 的普通组件来共享代码 class Mouse extends React.Component { static propTypes = { render: PropTypes.func.isRequired } state = { x: 0, y: 0 }; handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ); } } function App() { return ( <div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // render prop 给了咱们所须要的 state 来渲染咱们想要的 <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ); } ReactDOM.render(<App/>, document.getElementById('root'));
经过组合 Hooks API 和 React 内置的 Context,从前面的示例能够看到经过 Hook 让组件之间的状态共享更清晰和简单。
function FunctionalComponent () { const [state1, setState1] = useState(1); const [state2, setState2] = useState(2); const [state3, setState3] = useState(3); }
{ memoizedState: 'foo', next: { memoizedState: 'bar', next: { memoizedState: 'bar', next: null } } }
函数组件天生就是支持 props 的,基本用法上和 class 组件没有太大的差异。须要注意的两个区别是:
经过一个示例来理解一下 capture value,咱们能够经过 useRef 来规避 capture value,由于 useRef 是可变的。
class 组件 | 函数组件 | |
---|---|---|
建立状态 | this.state = {} | useState, useReducer |
修改状态 | this.setState() | set function |
更新机制 | 异步更新,屡次修改合并到上一个状态,产生一个副本 | 同步更新,直接修改成目标状态 |
状态管理 | 一个 state 集中式管理多个状态 | 多个 state,能够经过 useReducer 进行状态合并(手动) |
性能 | 高 | 若是 useState 初始化状态须要经过很是复杂的计算获得,请使用函数的声明方式,不然每次渲染都会重复执行 |
useEffect 在每一次渲染都会被调用,稍微包装一下就能够做为这些生命周期使用;
一般咱们优化组件性能时,会优先采用纯组件的方式来减小单个组件的渲染次数。
class Button extends React.PureComponent {}
React Hooks 中能够采用 useMemo 代替,能够实现仅在某些数据变化时从新渲染组件,等同于自带了 shallowEqual 的 shouldComponentUpdate。
因为默认状况下,每一次修改状态都会形成从新渲染,能够经过一个不使用的 set 函数来当成 forceUpdate。
const forceUpdate = () => useState(0)[1];
因为每个 Hooks API 都是纯函数的概念,使用时更关注输入 (input) 和输出 (output),所以能够更好的经过组装函数的方式,对不一样特性的基础 Hooks API 进行组合,创造拥有新特性的 Hooks。
import { useEffect } from 'react'; const useDidMount = fn => useEffect(() => fn && fn(), []); export default useDidMount;
import { useEffect, useRef } from 'react'; const useDidUpdate = (fn, conditions) => { const didMoutRef = useRef(false); useEffect(() => { if (!didMoutRef.current) { didMoutRef.current = true; return; } // Cleanup effects when fn returns a function return fn && fn(); }, conditions); }; export default useDidUpdate
在讲到 useEffect 时已经说起过,其容许返回一个 cleanup 函数,组件在取消挂载时将会执行该 cleanup 函数,所以 useWillUnmount 也能轻松实现~
import { useEffect } from 'react'; const useWillUnmount = fn => useEffect(() => () => fn && fn(), []); export default useWillUnmount;
// lib/onHover.js import { useState } from 'react'; const useHover = () => { const [hovered, set] = useState(false); return { hovered, bind: { onMouseEnter: () => set(true), onMouseLeave: () => set(false), }, }; }; export default useHover;
import { useHover } from './lib/onHover.js'; function Hover() { const { hovered, bind } = useHover(); return ( <div> <div {...bind}> hovered: {String(hovered)} </div> </div> ); }
// lib/useField.js import { useState } from 'react'; const useField = (initial) => { const [value, set] = useState(initial); return { value, set, reset: () => set(initial), bind: { value, onChange: e => set(e.target.value), }, }; } export default useField;
import { useField } from 'lib/useField'; function Input { const { value, bind } = useField('Type Here...'); return ( <div> input text: {value} <input type="text" {...bind} /> </div> ); } function Select() { const { value, bind } = useField('apple') return ( <div> selected: {value} <select {...bind}> <option value="apple">apple</option> <option value="orange">orange</option> </select> </div> ); }
React Hooks 提供为状态管理提供了新的可能性,尽管咱们可能须要额外去维护一些内部的状态,可是能够避免经过 renderProps / HOC 等复杂的方式来处理状态管理的问题。Hooks 带来的好处以下:
事实上,经过定制各类场景下的自定义 Hooks,能让咱们的应用程序更方便和简洁,组件的层次结构也能保证无缺,还有如此使人愉悦的函数式编程风格,Hooks 在 React 16.8.0 版本已经正式发布稳定版本,如今开始用起来吧!!!