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 (
    <>
      <div className={bulbState ? "bulbOn" : "bulbOff"}>我是灯泡</div>
      <button onClick={switchBulb}>开关</button>
    </>
  );
}
  • 使用普通更新:由于更新函数在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中所有使用该共享值的组件都会重新渲染
<MyContext.Provider value={xxx}>  

</MyContext.Provider>

// Consumer:消费者,使用共享数据
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
  1. useContext即为函数组件提供的获取共享数据的Hook
  2. 使用方法
const value = useContext(MyContext);

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

示例:

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

function App() {
  return (
    <PetContext.Provider value={animal.dog}>
      <MyPet />
    </PetContext.Provider>
  );
}

// 使用共享数据的子组件
function MyPet() {
  const pet = useContext(PetContext);
  return (
    <div>Hello, {pet.name}!</div>  // Hello, Kiki!
  );
}
  1. 使用Context的Provider组件包裹函数组件后,该函数组件才能共享上下文状态
<PetContext.Provider value={animal.dog}>
  <MyPet />
</PetContext.Provider>
  1. 当组件依赖的(上层距离最近的)Provider的value值发生变化时,useContext会触发重新渲染,获取最新的value值
  2. 使用useContext相当于在函数组件中订阅Context上下文的变化
// 在class组件中的订阅方式:
static contextType = MyContext
// 或
<MyContext.Consumer>

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    
  }
}

// ...
<button onClick={() => dispatch(action)}>点击增加用户</button>
  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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342