react hook学习的笔记

571 阅读5分钟

hooks的作用

hooks使得让函数组件具有类组件的能力。

state Hook

useState

  useState这个hook使得函数组件具有状态数据;具体用法如下:

const [state, setState] = useState(initValue);
// 更新state两种方式,前一种设置的值是不基于最新的state,而后一种是在最新的state的基础上设置的。
setState(statePlus);
setState((state) => statePlus)

  useState可以传入一个初始化的值,这个函数返回一个数组,数组的每一项分别是状态数据state和修改状态数局的函数setState。但是请注意:给useState传入的初始值和返回的初始值并不是指向同一个地址的。
  除此之外useState还可以声明多个,这样functional component就具有多个状态。

useReducer

  useReduceruseState类似,都可以解构两个值,不过useReducer解构出的两个值不一样。useReducer解构出的值分别是statedispatch方法。除此之外,useReducer传入的参数也不一样:

const [state, dispatch] = useReducer(reducerFn, initValue, initFn);

  上面的代码中use可以接受三个参数:改变state的纯函数、state的初始值、state的reset函数。

例子

import React, {Component, Fragment, useState, useEffect, useReducer} from 'react'
// 纯函数
function countReducer(state, action) {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      return state;
  }
}

export default function Func() {
  // state的形式
  const [stateCount, setCount] = useState(0);
  // reducer的形式
  const [reducerCount, dispatch] = useReducer(countReducer, 0);
  // useEffect相当于类组件的componentDidMount、componentDidUpdate、componentWillUnMount
  useEffect(() => {
    const timer = setInterval(() => {
      setCount((reducerCount) => reducerCount + 1)
      dispatch({type: 'add'})
    }, 1000);
    return () => clearInterval(timer)
  }, []);
  return (
      <Fragment>
          <span>{stateCount}</span>
          <span>{reducerCount}</span>
      </Fragment>
     )
}

Effect Hook

useEffect

  useEffect接受两个参数分别是callbackdeptch。前者解释为在组件挂载、更新和卸载阶段做的事,后者解释为执行这些事的依赖项。只有当依赖项发生改变时,才会执行回调函数。callback在卸载阶段时可以返回一个函数用来对副作用语句进行清除。

const [name, setName] = useState('');
useEffect(() => {
    console.log('effect invoked');
    return () => console.log('effect deteched')
 }, [name]);

  上面的代码中,只有当name发生改变时才会执行回调函数callback。
  重要的知识:

const [count, setCount] = useState(0);
useEffect(() => {
    const timer = setInterval(() => {
      setCount(count => count + 1);
    }, 1000);
    return () => clearInterval(timer)
}, []);

  上面的代码会在每一秒中更新一次count,但是我们依赖项是一个空数组,并不会发生改变,这为什么会每次都更新count呢?这是因为setCount(count => count + 1)在内部形成了闭包,看似没有发生改变,实际上每次更新的count是在前面的count基础上发生的变化,他们指向同一地址,所以不管你依赖项发生变化与否,count都会变。

Context Hook

useContext

  在使用useContext之前,我们需要得到从你要使用的是哪个context,所以我们需要创建一个context:

import React from 'react'
const Context = React.createContext('');

  在创建完成context之后,我们需要在根组件上用创建好的context下的Provider对其进行包裹和数据传递。

import React from 'react'
const context = React.createContext('');
class myApp extends App {
  state = {
    context: 'value',
  };
  // 更新context函数
  setContext = newContext => {
    this.setState(() => ({context: newContext}))
  };
  render() {
    // 这个Component即渲染的页面
    return (
        <context.Provider value={{context: this.state.context, updateContext: this.setContext}}>
          <B />
        </context.Provider>
    )
  }
}

function B(props){
    // 解构出context.Provider的value属性传递过来的context和updateContext
    const {context, updateContext} = useContext(context);
    return (
        // 通过updateContext进行context的更新
        <button onClick={() => updateContext('context updated')}>Update Context</button>
    )
}
export default myApp

  在上面的代码中,我们使用context.Providervalue属性进行值得传递,这个值我们可以传递任意类型的值,一般来说我们传递一个context和更新context的方法,这个时候就可以使用useReducer来实现redux的类似的操作。
  在子组件里我们使用useContext(context)来获取当前context传递过来的值,此时我们就需要对照传递过来的值进行数据的获取。

Ref Hook

  在以前的functional component中,我们是无法使用ref的。但是现在可以使用useRef在函数组件里面使用ref。

import React, {useState, useRef} from 'react'
export default function TestRef () {
  const InputRef = useRef(null);
  const [value, setValue] = useState('');
  const handleChange = () => {
    setValue(InputRef.current.value)
  };
  return (
      <input ref={InputRef} type="text" value={value} onChange={handleChange}/>
  )
}

Hooks渲染优化

  在使用hooks进行渲染优化时,我们经常memo、useMemo、useCallback这三个进行组件渲染的优化。
  总所周知,如果我们一个state发生变化的时候那么这个组件及其组件都会重新渲染,而这三个hook可以让react自身记住为变化之前的状态,从而达到优化的目的。

import React, {useReducer, useState, Fragment, memo, useMemo, useCallback} from 'react'

function countReducer(state, action) {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      return state;
  }
}
// 用memo来记住子组件:当回调函数和config都不变的情况下,子组件是不会更新的。
const Child = memo(function ({onButtonClick, config}) {
  console.log('child rendered');
  return (
      <button onClick={onButtonClick} style={{color: config.color}}>{config.text}</button>
  )
});

function MyCountFunc() {
  const [count, dispatchCount] = useReducer(countReducer, 0);
  const [name, setName] = useState('ainuo');
  // 用useMemo记住子组件需要的config数据:当count不发生变化时,config的地址始终不变,不会创建新的config
  const config = useMemo(() => ({
    text: `count is ${count}`,
    color: count > 3 ? 'red' : 'blue'
  }), [count]);
  // 使用useCallback记住事件的处理函数:当dispatchCount这个函数不发生变化时(实际上永远不会发生变化),回调函数就不会重新创建
  const handleButtonClick = useCallback(() => dispatchCount({type: 'add'}), [dispatchCount]);
  // const handleButtonClick = useMemo(() => () => dispatchCount({type: 'add'}), [dispatchCount])
  return (
      <Fragment>
        <input type="text" value={name} onChange={e => setName(e.target.value)}/>
        <Child config={config} onButtonClick={handleButtonClick}/>
      </Fragment>
  )
}
export default MyCountFunc

  从上面的代码中,我们可以看出useCallback其实就是useMemo对回调函数处理的优化。上述的几个记住非常重要,因为我们的组件和子组件的更新是依赖数据进行更新的,所以当依赖项不发生改变的时候子组件就不会发生重新渲染。这样可以减少组件树的构建和渲染,极大地优化网页。