1.引导

Hook是React16.8⼀一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

2.Hooks的特点:

  • 使你在无需修改组件结构的情况下复⽤状态逻辑
  • 可将组件中相互关联的部分拆分成更⼩的函数,复杂组件将变得更容易理解
  • 更简洁、更易理解的代码

3.useState

  1. import React, { useState } from 'react'
  2. function App() {
  3. const [count, setCount] = useState(0) // ES6解构语法
  4. return (
  5. <div>
  6. <h1>you click {count} times</h1>
  7. <input type="button" onClick={()=> setCount(count + 1)} value="click me" />
  8. </div>
  9. )
  10. }
  11. export default App

每次点击按钮时,setCount 方法接收的 count 都是最新状态,之后 React 会重新渲染组件并保留新的状态。

如果一个组件需要多个状态,我们可以在组件中多次使用 useState

4.useEffect

useEffect 给函数组件增加了执行副作用操作的能力。

副作⽤(Side Effect)是指⼀个 function 做了和本身运算返回值⽆关的事,⽐如:修改了全局变量、修改了传⼊的参数、甚⾄是 console.log(),所以 ajax 操作,修改 dom 都是算作副作⽤。

  1. import React, { useEffect, useState } from 'react'
  2. function App() {
  3. const [count, setCount] = useState(0)
  4. useEffect(() => {
  5. // 模拟发送请求信息,在一秒后改变 count 的值
  6. setTimeout(() => {
  7. setCount(10)
  8. }, 1000)
  9. })
  10. return (
  11. <div>{count}</div>
  12. )
  13. }
  14. export default App

测试会发现副作⽤操作会被频繁调⽤。

  • 设置依赖,让只执行一次副作用,如果副作⽤操作对某状态有依赖,务必添加依赖选项。
  1. // 设置空数组意为没有依赖,则副作⽤操作仅执行⼀一次
  2. useEffect(()=>{...}, [])
  • 清除⼯作:有⼀些副作用是需要清除的,清除⼯作非常重要的, 可以防⽌引起内存泄露。
  1. useEffect(() => {
  2. const timer = setInterval(() => {
  3. console.log('msg');
  4. }, 1000);
  5. return function(){
  6. clearInterval(timer);
  7. }
  8. }, []);

组件卸载后会执行返回的清理理函数。

5.useReducer

useReducer是useState的可选项,常⽤于组件有复杂状态逻辑时,类似于redux中reducer概念。

  1. const initialState = {count: 0};
  2. function reducer(state, action) {
  3. switch (action.type) {
  4. case 'increment':
  5. return {count: state.count + 1};
  6. default:
  7. throw new Error()
  8. }
  9. }
  10. import React, { useReducer } from 'react'
  11. function App() {
  12. const [state, dispatch] = useReducer(reducer, initialState)
  13. return (
  14. <div>
  15. <h1>you click {state.count} times</h1>
  16. <input type="button" onClick={()=> dispatch({type: 'increment'})} value="click me" />
  17. </div>
  18. )
  19. }
  20. export default App

以下场景,你可以优先使用 useReducer

  • state 变化很复杂,经常一个操作需要修改很多 state
  • 深层子组件里去修改一些状态;
  • 应用程序比较大,UI 和业务需要分开维护。

6.useContext

context 做的事情就是创建一个上下文对象,并且对外暴露提供者和消费者,在上下文之内的所有子组件,都可以访问这个上下文环境之内的数据,并且不用通过 props。 简单来说, context 的作用就是对它所包含的组件树提供全局共享数据的一种技术。

  1. import React, { useContext } from "react";
  2. const Context = React.createContext();
  3. const Provider = Context.Provider;
  4. export default function HookContext() {
  5. const store = { userName: "xiaoming" };
  6. return (
  7. <div>
  8. <h1>HookContext ⻚页⾯面</h1>
  9. <Provider value={store}>
  10. <Child />
  11. </Provider>
  12. </div>
  13. );
  14. }
  15. function Child(props) {
  16. const { userName } = useContext(Context);
  17. return (
  18. <div>
  19. Child
  20. <div>userName: {userName}</div>
  21. </div>
  22. );
  23. }

7.useMemo & useCallback

useCallbackuseMemo 的第一个参数是一个执行函数,第二个参数可用于定义其依赖的所有变量。如果其中一个变量发生变化,则 useMemo 或者 useCallback 会运行。这两个 API 都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,并且这两个 Hooks 都返回缓存的值,useMemo 返回缓存的变量, useCallback 返回缓存的函数。

  1. const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
  2. const memoizedCallback = useCallback(
  3. () => {
  4. doSomething(a, b);
  5. },
  6. [a, b],
  7. )

1.useMemo

通过使用 useMemo,我们可以将上一次执行的结果缓存在变量中,只有在 count 值变化时进行更新执行。

  1. function WithoutMemo() {
  2. const [count, setCount] = useState(1)
  3. const [val, setValue] = useState('')
  4. const expensive = useMemo(() => {
  5. console.log('compute')
  6. // 假设是个计算量比较大的函数
  7. return count * 100
  8. }, [count])
  9. return (
  10. <div>
  11. <h4>
  12. {count}-{val}-{expensive}
  13. </h4>
  14. <div>
  15. <button onClick={() => setCount(count + 1)}>+c1</button>
  16. <input value={val} onChange={event => setValue(event.target.value)} />
  17. </div>
  18. </div>
  19. )
  20. }

2.useCallback

当我们父组件中包含一个子组件,子组件接收一个函数作为 props,通常的情况是,如果父组件更新,那么子组件也会随之更新。但我们知道,大多数情况下子组件的更新是没有必要的,这时我们可以借助 useCallback 来解决这个问题,通过 useCallback 返回的函数,然后把这个缓存的函数传递给子组件,这样只有当这个函数发生变化时,子组件才会更新。一起来看下例子:

  1. // 子组件
  2. function SubComponent(props){
  3. console.log('SubComponent render');
  4. return (
  5. <button onClick={props.onClick}>{props.data.count}</button>
  6. )
  7. }
  8. // 父组件
  9. const SubComponent = memo(SubComponent);
  10. export default function WithMemo(){
  11. console.log('Counter render');
  12. const [val, setValue]= useState('计数器');
  13. const [count,setCount] = useState(0);
  14. const data = useMemo(()=>({count}),[count]);
  15. // 有没有后面的依赖项数组很重要,否则还是会重新渲染
  16. const addClick = useCallback(()=>{
  17. setCount(count+1);
  18. }, [count]);
  19. return (
  20. <>
  21. <input value={val} onChange={event => setValue(event.target.value)} />
  22. <SubComponent data={data} onClick={addClick}/>
  23. </>
  24. )
  25. }

8.useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。

  1. function ChildComponent() {
  2. const inputEl = useRef(null)
  3. const curFocus = () => {
  4. // `current` 指向已挂载到 DOM 上的文本输入元素
  5. inputEl.current.focus()
  6. }
  7. return (
  8. <>
  9. <input ref={inputEl} type="text" />
  10. <button onClick={curFocus}>Focus the input</button>
  11. </>
  12. )
  13. }

9.useLayoutEffect

其函数签名与 useEffect 相同,useEffect 是在全部渲染完之后才会执行,而 useLayoutEffect 会在浏览器布局之后,绘制之前执行。useLayoutEffect 是在页面绘制之前执行,因此会阻塞视图更新。

  1. function LayoutEffect() {
  2. const [color, setColor] = useState("red")
  3. useLayoutEffect(() => {
  4. alert(color)
  5. })
  6. useEffect(() => {
  7. console.log('color', color)
  8. })
  9. return (
  10. <>
  11. <div id="myDiv" style={{ background: color }}>
  12. 颜色
  13. </div>
  14. <button onClick={() => setColor('red')}>红</button>
  15. <button onClick={() => setColor('yellow')}>黄</button>
  16. <button onClick={() => setColor('blue')}>蓝</button>
  17. </>
  18. )

10.自定义hooks

Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。

  1. const usePerson = (personId) => {
  2. const [loading, setLoading] = useState(true);
  3. const [person, setPerson] = useState({});
  4. useEffect(() => {
  5. setLoading(true);
  6. fetch(`https://swapi.co/api/people/${personId}/`)
  7. .then(response => response.json())
  8. .then(data => {
  9. setPerson(data);
  10. setLoading(false);
  11. });
  12. }, [personId]);
  13. return [loading, person];
  14. }
  15. const Person = ({ personId }) => {
  16. const [loading, person] = usePerson(personId);
  17. if (loading === true) {
  18. return <p>Loading ...</p>;
  19. }
  20. return (
  21. <div>
  22. <p>You're viewing: {person.name}</p>
  23. <p>Height: {person.height}</p>
  24. <p>Mass: {person.mass}</p>
  25. </div>
  26. );
  27. };