前言:一直对这个新特性很是感兴趣,终于今天有时间,花了大半天时间,把
Hooks
的官方教程过了一遍,收获颇多,惊叹这个新特性真 TM 好用,之后开发用这个怕是要起飞了😆。
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
判断),将会退出更新。❌用法:html
const [rows, setRows] = useState(createRows(props.count)); // `createRows()`每次将会渲染将会被调用
✅用法:react
const [rows, setRows] = useState(() => createRows(props.count)); // `createRows()`只会被调用一次
其中的() => createRows(props.count)
会赋值给rows
,这样就保证了只有在rows
调用时,才会建立新的值。git
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 } } props ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect // Update with { friend: { id: 200 } } props ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect // Update with { friend: { id: 300 } } props ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect // Unmount ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
useEffect(() => {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
老是在开始更新以前刷新以前渲染的效果。const context = useContext(Context);
接受一个上下文对象(由React.createContext
建立),返回当前上下文值(由最近的上下文提供)。npm
基本钩子的变体或用于特定边缘状况的钩子。数组
const [state, dispatch] = useReducer(reducer, initialArg, init);
init
为函数,将会这样调用:init(initialArg)
,返回初始值。state
和如今的state
同样,将会在不影响子孙或者触发效果的状况下退出渲染。const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
传入一个内联回调和一个输入数组,返回一个带有记忆的函数,只有输入数组中其中一个值变化才会更改。useCallback(fn, inputs)
等价于 useMemo(() => fn, inputs)
。浏览器
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
传入一个建立函数和一个输入数组,返回一个带有记忆的值,只有输入数组中其中一个值变化才会从新计算。app
const refContainer = useRef(initialValue); // ... <input ref={refContainer} /> ...
返回一个可变的ref
对象,能够自动将ref
对象中的current
属性做为初始值传递的参数,保持到组件的整个生命周期。dom
与在类中使用实例字段的方式相似,它能够保留任何可变值。函数
如保存前一个状态:布局
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>; }
useImperativeHandle(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
一致,不过它是在 DOM 读取布局时同步触发(至关于componentDidMount
和componentDidUpdate
阶段)。(建议尽量使用useEffect
避免阻塞可视化更新)
useDebugValue(value)
用于在React DevTools
中显示自定义钩子的标签,对于自定义钩子中用于共享的部分有更大价值。
自定义显示格式:
useDebugValue(date, date => date.toDateString());
正确作法:
useEffect(function persistForm() { // 👍 We're not breaking the first rule anymore if (name !== '') { localStorage.setItem('formData', name); } });
React
函数组件中被调用。(能够经过自定义钩子函数解决)可使用eslint-plugin-react-hooks来强制自动执行这些规则。
use
开头,一种公约。useState
和useEffect
是彻底独立的。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> ); }
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'); });