React Hooks

React Hooks 是 React v16.8 版本引入了全新的 API,其设计目的就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

一、作用

Hook 这个单词的意思是"钩子"。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用 use 前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

下面介绍 React 默认提供的钩子。

* useState()
* useEffect()
* useContext()
* useReducer()
* useMemo()
* useRef()
二、useState()

useState() 用于为函数组件引入状态(state)。纯函数只有属性不能有状态,所以把状态放在钩子里面。

import React, {useState} from 'react';

const [n, setN] = useState(0);
const [user, setUser] = useState({name: 'Tom', age: '18'}];

useState() 这个函数接受状态的初始值,作为参数。该函数返回一个数组,数组的第一个成员是一个变量(上例是 n / user),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setN / setUser)。

  1. 更新状态方法一:setxxx() 括号里面直接修改
setN(n + 1)
setUser({
  ...user,
  name: 'Jerry'
})

需要注意的是 setxxx 不能局部更新:

setUser(name: 'Jerry')
// 这样做的结果是对象最后只有一个 name 属性了
  1. 更新状态方法二:setxxx() 括号里面写函数
setN( i => i + 1)

因为 setxxx 是异步更新,n 的值不会马上改变,如果在 setN 的作用域内紧接着对 n 进行下一步操作的话,最终只会执行最后一步操作,所以如果需要对 n 进行多步操作的话就使用传函数的方法。

三、useEffect()

useEffect() 接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect() 就会执行。

  1. 用法
  • 第二个参数传 [] 空数组,作为 componentDidMount 使用,组件第一次渲染的时候执行
import React, {useEffect} from 'react';

useEffect(() => {
  // Async Action
}, [])
  • 第二个参数传变量,当变量变化后组件渲染执行
  • 第二个参数不传,当任何变量变化后组件每一次渲染执行
    上面两种都是作为 componentDidUpdate 使用,但是组件第一次渲染的时候也会执行,如果不需要第一次执行可以设置一个判断语句:
useEffect(() => {
  if (n !== 初始值) {
    // actions
  }
}, [n])
  • 还可以作为 componentWillUnmount 使用,在操作完成后 return 就可以了
useEffect(() => {
  let timer = setInterval(() => {console.log('hi')}, 1000)
  return () => {clearInterval(timer)
}, [])
  1. 特点
    允许多个 useEffect 同时存在,如果存在多个,会按照出现顺序依次执行。
四、useContext()

如果需要在组件之间共享数据,可以使用useContext()

  1. 使用 name = createContext(defaultValue) 创建一个共享数据
  2. 使用 <name.provider></name.provider> 圈定其作用范围,一般会结合 useState 来操作数据
const C = createContext(null)
function App() {
  const [n, setN] = useState(0)
  return (
    <C.provider value={{n: n, setN: setN}}>
      <div className="App">
         <Child1 />
         <Child2 ><Grandson /></Child2>
      </div>
    </C.provider>
  )
}
  1. 范围内的所有组件及子孙组件都可以使用 useContext(name) 来使用该数据
function Grandson() {
  const {n, setN} = useConText(C)
  const click = () => { setN(i => i + 1) }
  return (
    <div>
      {n}
      <button onClick={click}>+1</button>
    </div>
  )
}
  1. 需要注意的是 react 可以有 Context 的嵌套,如果存在这种情况,而且嵌套的 Context 还提供了相同的方法,子组件只会匹配最近的 provider
五、useReducer()

可以看成是 useState 的复杂版,优点是可以把多个重复使用的方法汇聚起来,使代码更简洁。

  • useState 提供一个初始值,返回一个变量和一个能够操作该变量的函数,具体操作自己在函数里面定义执行;
  • useReducer 提供一个初始值和一个函数,函数里面给对数据的不同操作做标记,返回一个变量和一个能够对应标记提取操作的函数。
const [state, dispatch] = useReducer(reducer, initialState)

用法:

  1. 创建初始值
const initialState = {
    n: 0,
    m: 2
}
  1. 创建不同操作标记
const reducer = (state, action) => {
    switch(action.type) {
        case('add'):
            return {...state, n: state.n+1};
        case('subtract'):
            return {...state, m: state.m-action.number);
        default: return state;
    }
}
  1. 使用
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return  (
    <div className="App">
      <div>
        <button onClick={() => dispatch({ type: 'add' })}>
          n+1
        </button>
        <p>n: {state.n}</p>
      </div>
      <div>
        <button onClick={() => dispatch({ type: 'subtract', number: 2 })}>
          m-2
        </button>
        <p>m: {state.m}</p>
      </div>
    </div>
  );
}
六、useMemo()

要讲 useMemo 首先要了解 memo,memo类似于PureCompoent 作用是优化组件性能,防止组件触发重渲染

function App() {
  const [n, setN] = useState(0);
  const [m, setM] = useState(0);
  const click = () => {
    setN(n + 1)
  }

  return  (
    <div className="App">
      <div>
        <button onClick={click}>update n {n}</button>
      </div>
      <Child2 data={m} />
    </div>
  );

  function Child(props) {
    console.log('假如有大量代码')
    return <div>child: {props.data}</div>
  }

  const Child2 = React.memo(Child)
}

Child 经过 memo 后当点击按钮的时候就不会重新渲染了,但是当 Child 有函数逻辑的时候:

const clickChild = () => {}
  return  (
    <div className="App">
      <div>
        <button onClick={click}>update n {n}</button>
      </div>
      <Child2 data={m} onClick={clickChild} />
    </div>
  )

即使 memo 了它依然还是会重新渲染,因为当 n 变化的时候 APP 会重新渲染,clickChild = () => {} 重新运行,得到另外一个空函数,地址不同组件内容改变,所以 Child 还是会重新渲染,这时候就可以使用 useMemo 来优化了。

const clickChild = useMemo(() => {
  return () => {}
}, [])

把 Child 的所有函数逻辑全部用 useMemo 返回出来就好了,后面的数组和 useEffect 的用法一样,忽略就每次返回新函数,空数组第一次返回,把和函数逻辑相关的变量放进去就是每次变量变化的时候返回新函数。
因为 useMemo 本身是个函数又返回一个函数,写起来就有点多,可以用语法糖 useCallback 即:

const clickChild = useCallback(() => {}, [])

对比可以看出,memo是针对 一个组件的渲染是否重复执行,useMemo是针对 一段函数逻辑是否重复执行。

七、useRef()

因为在 React 的函数组件中,一个全局变量会因为组件重新渲染而重复声明,就导致每次都是一个新的变量地址,就没法对上一次的变量进行操作了,如果想要这个变量在始终可操作可以用 useRef 来实现:
useRef() 返回一个可变的 ref对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

function App () {
  const [ count, setCount ] = useState(0)
  const timer = useRef(null)
  let timer2 
  
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count => count + 1)
    }, 500)

    timer.current = id
    timer2 = id
    return () => {
      clearInterval(timer.current)
    }
  }, [])

  const onClickRef = useCallback(() => {
    clearInterval(timer.current)
  }, [])

  const onClick = useCallback(() => {
    clearInterval(timer2)
  }, [])

  return (
    <div>
      点击次数: { count }
      <button onClick={onClick}>普通</button>
      <button onClick={onClickRef}>useRef</button>
    </div>
    )
}
八、自定义 Hook

React Hooks 最厉害的地方就是它可以自定义 Hook,它可以把全局数据和方法放在指定的地方统一管理,组件用到了可以直接引入调用,自定义 Hook 需要遵守一个规则就是命名必须以 use 开头。下面举个例子,因为 useEffect 总是在组件第一次渲染就会运行,我们可以自定义一个第一次不运行,当依赖变化的时候再运行:

import {useEffect, useRef} from 'react'

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