前言:一直对这个新特性非常感兴趣,终于今天有时间,花了大半天时间,把 Hooks的官方教程过了一遍,收获颇多,惊叹这个新特性真 TM 好用,以后开发用这个怕是要起飞了????。状态钩子(State Hook)const [state, setState] = useState(initialState);多个useState时,React依赖于每次渲染时钩子的调用顺序都是一样的(存在与每个组件关联的“存储单元”的内部列表存放JavaScript对象),从而实现钩子与状态的一一对应关系。setState()接收新的state或者一个返回state的函数(setCount(prevCount => prevCount - 1)})。不同于类组件中的setState,useState返回的setState 不会自动合并更新对象到旧的state中(可以使用useReducer)。useState可以接收一个函数返回initialState,它只会在初次渲染时被调用。当setState中的state和当前的state相等(通过Object.is判断),将会退出更新。建议将一个状态根据哪些需要值一起变化拆分为多个状态变量。❌用法:const [rows, setRows] = useState(createRows(props.count)); // createRows()每次将会渲染将会被调用✅用法:const [rows, setRows] = useState(() => createRows(props.count)); // createRows()只会被调用一次其中的() => createRows(props.count)会赋值给rows,这样就保证了只有在rows调用时,才会创建新的值。作用钩子(Effect Hook)useEffect(didUpdate);相当于生命周期函数componentDidMount, componentDidUpdate, componentWillUnmount的组合。可以返回一个函数(cleanup)用于清理。每次重新渲染都将会发生cleanup phase⏬useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } // ====== 原因在这里 ====== componentDidUpdate(prevProps) { // Unsubscribe from the previous friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // Subscribe to the next friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }// Mount with { friend: { id: 100 } } propsChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect// Update with { friend: { id: 200 } } propsChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effectChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect// Update with { friend: { id: 300 } } propsChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effectChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect// UnmountChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effectuseEffect(() => {document.title = You clicked ${count} times;}, [count]); ,指定第二个参数(这里为[count])变化时才发生cleanup phase,然后执行effect;上面情况,如果useEffect第二个参数为为[]则表示只运行一次(componentDidMount中执行effect,componentWillUnmount中进行cleanup),永远不重新运行。和componentDidMount/componentDidUpdate有区别的地方在于,useEffect中的函数会在layout和paint结束后才被触发。(可以使用useLayoutEffect在下一次渲染之前(即 DOM 突变之后)同步触发)useEffect虽然被推迟到浏览器绘制完成之后,但是肯定在有任何新的呈现之前启动。因为React总是在开始更新之前刷新之前渲染的效果。其他钩子useContextconst context = useContext(Context);接受一个上下文对象(由React.createContext创建),返回当前上下文值(由最近的上下文提供)。附加钩子(Additional Hooks)基本钩子的变体或用于特定边缘情况的钩子。useReducerconst [state, dispatch] = useReducer(reducer, initialArg, init);第三个参数init为函数,将会这样调用:init(initialArg),返回初始值。如果返回state和现在的state一样,将会在不影响子孙或者触发效果的情况下退出渲染。useCallbackconst memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b],);传入一个内联回调和一个输入数组,返回一个带有记忆的函数,只有输入数组中其中一个值变化才会更改。useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)。useMemoconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);传入一个创建函数和一个输入数组,返回一个带有记忆的值,只有输入数组中其中一个值变化才会重新计算。useRefconst refContainer = useRef(initialValue);// …<input ref={refContainer} />…返回一个可变的ref对象,可以自动将ref对象中的current属性作为初始值传递的参数,保持到组件的整个生命周期。与在类中使用实例字段的方式类似,它可以保留任何可变值。如保存前一个状态:function Counter() { const [count, setCount] = useState(0); const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = count; }); const prevCount = prevCountRef.current; return <h1>Now: {count}, before: {prevCount}</h1>;}useImperativeHandleuseImperativeHandle(ref, createHandle, [inputs])自定在使用 ref 时,公开给父组件的实例值,必须和forwardRef一起使用。function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} … />;}FancyInput = forwardRef(FancyInput);<FancyInput ref={fancyInputRef} />// 调用fancyInputRef.current.focus()useLayoutEffect使用方法和useLayoutEffect一致,不过它是在 DOM 读取布局时同步触发(相当于componentDidMount和componentDidUpdate阶段)。(建议尽可能使用useEffect避免阻塞可视化更新)useDebugValueuseDebugValue(value)用于在React DevTools中显示自定义钩子的标签,对于自定义钩子中用于共享的部分有更大价值。自定义显示格式:useDebugValue(date, date => date.toDateString());钩子(Hooks)规则1. 只能在顶层调用,不能再循环、条件语句和嵌套函数中使用。 (原因:State Hook 第1条)正确做法:useEffect(function persistForm() { // ???? We’re not breaking the first rule anymore if (name !== ‘’) { localStorage.setItem(‘formData’, name); } });2. 只能在React函数组件中被调用。(可以通过自定义钩子函数解决)可以使用eslint-plugin-react-hooks来强制自动执行这些规则。自定义钩子(Hook)以use开头,一种公约。自定钩子是一种复用状态逻辑的机制(例如设置订阅和记住当前值),每次使用,内部所有状态和作用都是独立的。自定义钩子每个状态独立的能力源于useState和useEffect是完全独立的。测试钩子(Hook)function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = You clicked ${count} times; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );}使用ReactTestUtils.act()import React from ‘react’;import ReactDOM from ‘react-dom’;import { act } from ‘react-dom/test-utils’;import Counter from ‘./Counter’;let container;beforeEach(() => { container = document.createElement(‘div’); document.body.appendChild(container);});afterEach(() => { document.body.removeChild(container); container = null;});it(‘can render and update a counter’, () => { // Test first render and effect act(() => { ReactDOM.render(<Counter />, container); }); const button = container.querySelector(‘button’); const label = container.querySelector(‘p’); expect(label.textContent).toBe(‘You clicked 0 times’); expect(document.title).toBe(‘You clicked 0 times’); // Test second render and effect act(() => { button.dispatchEvent(new MouseEvent(‘click’, {bubbles: true})); }); expect(label.textContent).toBe(‘You clicked 1 times’); expect(document.title).toBe(‘You clicked 1 times’);});建议使用react-testing-library参考Hooks