React Hooks产生的原因
由于类组件存在一些缺点:
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式,比如 render props 和高阶组件。
于是有了函数式组件。一般的编程原则是一个函数只做一件事,那么如果这个函数组件要进行日志存储、改变状态等操作时如何处理呢?
函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) 。钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。
组件类型
Functional(Stateless)Component
功能组件也叫无状态组件,一般只负责渲染。
1 2 3 4
| function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
|
Class(Stateful)Component
类组件也是有状态组件,一般有交互逻辑和业务逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Welcome extends React.Component { state = { name: ‘tori’, } componentDidMount() { fetch(…); … } render() { return ( <> <h1>Hello, {this.state.name}</h1> <button onClick={() => this.setState({name: ‘007’})}>改名</button> </> ); } }
|
Presentational Component
和功能组件类似
1 2 3 4 5 6 7 8
| const Hello = (props) => { return ( <div> <h1>Hello! {props.name}</h1> </div> ) }
|
常用Hooks
useState
状态管理,某个状态发生变化,组件重新渲染,使用该状态的组件及其子组件都会重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function App() { return ( <div> <ComponentA /> <ComponentB /> </div> ); }
function ComponentA() { const [count, setCount] = useState(0);
return ( <div> <button onClick={() => setCount(count + 1)}>Click me</button> // ComponentA、ComponentC会重新渲染 <ComponentC /> </div> ); }
function ComponentB() { }
function ComponentC() { }
|
useEffect
在函数组件中执行副作用操作,方便,且避免不必要的bug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { useState, useEffect } from 'react';
function Example() { const [count, setCount] = useState(0);
useEffect(() => { const timerId = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000);
return () => { clearInterval(timerId); }; }, []);
return <div>{count}</div>; }
|
useRef
用于在不进行渲染的情况下存储和访问最新的值
- 访问 DOM 元素:你可以将 ref 对象赋值给 JSX 元素的 ref 属性,然后在其他地方通过 .current 属性访问这个元素。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
|
- 存储可变值:ref 对象的 .current 属性是可变的,它可以用来存储任何可变值,而不仅仅是 DOM 元素。与在组件中使用 state 不同,修改 ref 对象的 .current 属性不会触发组件重新渲染。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Timer() { const intervalId = useRef();
useEffect(() => { intervalId.current = setInterval(() => { console.log('Timer tick'); }, 1000);
return () => { clearInterval(intervalId.current); }; }, []);
}
|
useReducer
接受一个reducer函数和一个初始值,返回新的状态和一个dispatch函数,通过调用dispatch函数来更新状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { useReducer } from 'react';
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() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
|
useMemo
useMemo(fn, deps);
缓存计算结果。fn 是产生所需数据的一个计算函数。通常来说,fn 会使用 deps 中声明的一些变量来生成一个结果,用来渲染出最终的 UI。
好处:
useCallback
useCallback(fn, deps)
缓存函数。fn 是定义的回调函数,deps 是依赖的变量数组。只有当某个依赖变量发生变化时,才会重新声明 fn 这个回调函数。useCallback 的功能其实是可以用 useMemo 来实现的
1 2 3 4 5 6 7 8 9 10
| import React, { useState, useCallback } from 'react';
function Counter() { const [count, setCount] = useState(0); const handleIncrement = useCallback( () => setCount(count + 1), [count], ); return <button onClick={handleIncrement}>+</button>; }
|
useContext
定义全局状态。React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。那么在函数组件里,我们就可以使用 useContext 这样一个 Hook 来管理 Context。
1 2 3 4 5 6 7 8 9 10
|
const MyContext = React.createContext(initialValue);
const value = useContext(MyContext);
|
useImperativeHandle
自定义Hooks
参考