React Hooks的使用总结

Hook使用规则

  1. 确保在React函数顶层使用Hooks,不能在循环、条件、嵌套函数中调用:当单个组件中有多个Hook时,React通过Hooks的调用顺序来保证Hooks的状态正确
  2. 只在函数组件或自定义Hook中调用

常用Hooks

useState

  1. 在函数组件内部使用useState,可以使该组件变成有状态组件。这样在不使用class组件情况下也可以使用state及其他特性
  2. 使用方法
const [state, setState] = useState(initialState);

参数:状态初始值,可以是变量或函数

  • 变量:初始渲染期间,state与initialState的值相同
  • 函数:如果初始参数需要通过复杂的计算得到,可以传入一个函数,返回initialState。当初始状态需要通过高性能操作后才能获得时,使用函数获取初始值可以提高性能:该函数只在初始渲染时被调用,在后续组件渲染中不会再调用

返回值:包含两个元素的数组,可解构

  • state:状态值
  • setState:状态更新函数。调用后,重新渲染组件,状态更新到最新
  1. 惰性初始stateinitialState只会在组件的初始渲染中起作用,后续渲染时会被忽略
  2. 函数式更新:新的state依赖于先前的state时,可以为setState方法传入一个函数,函数接收先前的state,返回更新后的值
  3. 跳过state更新:若为setState传入当前state,React将跳过子组件的渲染及effect的执行
  4. 在多个useState调用中,渲染之间的调用顺序必须相同
  5. 函数式更新可以防止合并更新问题
function Bulb(props) {
  const [bulbState, setBulbState] = useState(false);
  const switchBulb = () => {
    setTimeout(() => {
      setBulbState(!bulbState);
      // setBulbState(bulbState => !bulbState);
    }, 3000);
  };
  return (
    <>
      
我是灯泡
); }
  • 使用普通更新:由于更新函数在setTimeout中调用,在3s内多次点击时,多次state更新会合并成一次,只改变一次state的状态
setBulbState(!bulbState);
  • 使用函数式更新:在3s内多次(n)点击时,每次的更新函数确保收到的是最新的状态,所以会在3s后切换n次状态
setBulbState(bulbState => !bulbState);

useEffect

  1. useEffect用于执行函数组件中的副作用(Side Effects)。副作用一般包含请求数据、事件处理、订阅、改变DOM等操作。React要求函数组件必须是一个纯函数,其中不能包含副作用。因此,React Hooks提供useEffect,以便在函数组件中执行副作用操作
  2. useEffect默认在每次渲染结束后执行
  3. 使用方法
useEffect(() => {
  // Todo
}, [xxx]);

参数:包含命令式、可能有副作用的函数。第二个参数可选,为副作用依赖值的数组

  • 当无第二个参数时:每次组件渲染完成后都会执行
  • 当第二个参数为[]时:空数组表示effect中没有依赖props或state中的值,因此仅执行一次(仅在组件挂载和卸载时执行)。类似于class组件中的componentDidMountcomponentWillUnmount
  • 当第二个参数为变量数组时:当数组中的变量发生变化时,执行effect。如果数组中包含多个变量,当该次渲染的数组与上一次渲染的数组中的元素都相等时,会跳过本次effect的执行,但即使只有一个元素发生变化,也会执行。类似于componentDidUpdate中对prevPropsprevState的判断
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

注意:

  1. 每次渲染都执行effect可能会导致性能问题,利用useEffect的第二个参数可以跳过 Effect 的执行,从而进行性能优化
  2. 数组中包含的是所有外部作用域中会随时间变化并且在effect中使用的变量
  1. 两种副作用
  • 不需要清除的effect:在渲染完成后执行的一些额外的代码,执行后即可忽略。如发送网络请求,手动变更 DOM,记录日志等
  • 需要清除的effect:如订阅外部数据源、定时器操作等,effect执行后要进行清除操作,以防止内存泄露
  1. 清除effect:不需要单独的effect来执行清除操作,只需在effect中返回一个清除函数,React将会在执行清除操作时调用该函数
    清除时机:React会在组件卸载时清理effect,由于effect默认每次渲染时都会执行,所以React会在调用一个新的 effect 之前对前一个 effect 进行清理
useEffect(() => {
  // Todo
  return () => {
    // 清除effect操作
  }
});
  1. useEffect会在浏览器绘制后、新的渲染前执行,因此不会阻止浏览器的页面渲染

useContext

  1. Context为组件树提供数据共享的方法,数据无需层层传递,即不需要该数据的中间层组件无需作为“快递员”传递数据,避免props层层传递过于繁琐、组件关系维护困难。具体见文档
// 创建一个Context上下文
// defaultValue:初始共享值
const MyContext = React.createContext(defaultValue);

// Provider:提供者,提供共享数据
// value即共享数据
// 当value值发生变化时,Provider中所有使用该共享值的组件都会重新渲染
  



// Consumer:消费者,使用共享数据

  {value => /* 基于 context 值进行渲染*/}

  1. useContext即为函数组件提供的获取共享数据的Hook
  2. 使用方法
const value = useContext(MyContext);

参数:通过React.createContext创建的上下文组件对象
返回值:该上下文的当前值,即距离当前组件最近的value

示例:

const animal = {
  dog: {
    name: 'Kiki',
    age: 1
  },
  cat: {
    name: 'Sara',
    age: 2
  }
}
// 创建上下文,传入初始值
const PetContext = React.createContext(animal);

function App() {
  return (
    
      
    
  );
}

// 使用共享数据的子组件
function MyPet() {
  const pet = useContext(PetContext);
  return (
    
Hello, {pet.name}!
// Hello, Kiki! ); }
  1. 使用Context的Provider组件包裹函数组件后,该函数组件才能共享上下文状态

  

  1. 当组件依赖的(上层距离最近的)Provider的value值发生变化时,useContext会触发重新渲染,获取最新的value值
  2. 使用useContext相当于在函数组件中订阅Context上下文的变化
// 在class组件中的订阅方式:
static contextType = MyContext
// 或

useReducer

  1. useReduceruseState的替代方案,与Redux中的reducer功能类似,都是提供状态改变功能;相对于useState来说,useReducer更适用于state逻辑复杂且包含多个子值(对象数组嵌套结构)的情景
  2. 使用方法:
const [state, dispatch] = useReducer(reducer, initialArg, init);

参数

  • reducer:一个函数,接收当前状态state和触发行为action,返回计算后的新的状态newState
(state, action) => newState

action:触发状态改变的行为,是一个对象,包含:
type:表示行为的描述,如增、删、改、查用户等;
payload(可选):表示携带的数据,用于newState的计算

const action = {
  type: 'addUser',
  payload: {
    name: 'Bobo',  
    sex: 0    
  }
}

注意:

  • 不要直接修改state!因为它是immutable(React 使用 Object.is 来比较 state
  • 可以通过解构赋值创建newState并返回

举个栗子:

function myReducer(state, action) {
  const {type, payload} = action
  if (type === 'addUser') {
     return [
      ...state,
      payload
     ]
  }
  if (type === 'removeUser') {
     // 移除该用户,并把新的用户数组返回
     // ...
  }
  // type的其他判断...
  return state
}
  • initialArg:初始状态值
  • init:一个方法,用于惰性初始化state,当传该方法时,初始值就变成了 init(initialArg),可以用来重置初始状态

返回值:包含两个元素的数组,可解构

  • state:当前状态值
  • dispatch:方法,用于触发事件以更新state,参数为一个action对象,即将action中的payload作为参数,执行type操作,更改状态
// ...
const action = {
  type: 'addUser',
  payload: {
    name: 'Bobo',  
    sex: 0    
  }
}

// ...

  1. 跳过dispatch:若返回的state与当前的state相同,则不会进行子组件的重新渲染和执行副作用
  2. useReducer可以结合useContext执行复杂的状态更新操作。当组件嵌套比较深时,可以将dispatch作为共享数据传递给子组件,这样在子组件中就可以使用dispatch触发事件,更改状态。参考此处

useCallback

  1. useCallback用于缓存回调函数,减少不必要的渲染
  2. 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回新的函数
  3. 使用方法
useCallback(fn, deps)

参数

  • fn:函数
  • deps:依赖项

返回值:缓存的函数

useMemo

  1. useCallback类似,useMemo用于针对返回值进行缓存优化
  2. 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回新的值
  3. 使用方法
useMemo(() => fn, deps)

参数

  • fn:函数
  • deps:依赖项

返回值:缓存的值

useRef

  1. 先来了解一下Refs和DOM,在HTML元素或class组件上可以通过 React.createRef()创建ref引用该元素
  2. 在函数组件中引入了useRef,用于保存任何可变的值,每次都会返回相同的ref引用
  3. 使用方法
const refContainer = useRef(initialValue);

参数

  • initialValue:初始值

返回值:可变的 ref 对象,其.current属性被初始化为传入的参数initialValue

你可能感兴趣的:(React Hooks的使用总结)