React Hooks使用实例

hooks存在的问题:

不能完全替代Class:

像 getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 等生命周期 API,使用 Hooks 不能完全替代。

复杂组件的乘载能力弱:

一个复杂的 Class 业务组件,实例可能挂载非常多的方法,如果用函数式组件 Hooks 的方式来实现的话,如何让组件的业务逻辑看上去清晰明了,如何保证组件后续的可维护性,这确实是比较大的挑战。

上手易用好难:

上手容易是指通查使用 Hooks 只需要学习 useState 和 useEffect 就能满足大部分组件的基础功能。但后续使用会遇到较多问题,如Hooks Rules 的约定、Hooks 依赖项的判断,Hooks 闭包问题,Hooks 性能优化等,以上都需要开发者有一定实践经验才能较好的完成。

使用hooks注意事项:

  • 不能将 hooks 放在循环、条件语句或者嵌套方法内。react 是根据 hooks 出现顺序来记录对应状态的。
  • 只在 function 组件和自定义 hooks 中使用 hooks。

  • 命名规范:
    • useState 返回数组的第二项以 set 开头(仅作为约定)。

    • 自定义 hooks 以 use 开头(可被 lint 校验)。

React 中提供的 hooks:

  • useState:setState

  • useReducer:setState,同时 useState 也是该方法的封装

  • useRef: ref

  • useImperativeHandle: 给 ref 分配特定的属性

  • useContext: context,需配合 createContext 使用

  • useMemo: 可以对 setState 的优化

  • useCallback: useMemo 的变形,对函数进行优化

  • useEffect: 类似 componentDidMount/Update, componentWillUnmount,当效果为 componentDidMount/Update 时,总是在整个更新周期的最后(页面渲染完成后)才执行

  • useLayoutEffect: 用法与 useEffect 相同,区别在于该方法的回调会在数据更新完成后,页面渲染之前进行,该方法会阻碍页面的渲染

  • useDebugValue:用于在 React 开发者工具中显示自定义 hook 的标签

1.使用useState实现改变值

  • useState 有一个参数,该参数可传如任意类型的值或者返回任意类型值的函数

  • useState 返回值为一个数组,数组的第一个参数为我们需要使用的 state,第二个参数为一个setter函数,可传任意类型的变量,或者一个接收 state 旧值的函数,其返回值作为 state 新值。

在index.tsx中的代码如下:

import React, { useState } from 'react';

export default () => {
  const [price, setPrice] = useState(0)
  const [count, setCount] = useState(()=>{
    const price = 5
    return price;
  })
  return (
    <div>
      <div>单价:{price}元</div>
      <button onClick={()=>setPrice(price+1)}>AddPrice</button>
      <div>数量:{count}个</div>
      <button onClick={()=>setCount(count+1)}>AddCount</button>
    </div>
  );
}

效果如图:通过setPrice和setCount改变price和count的值;

2.使用useContext传递值

设计目的: context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据。

使用场景: context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性

注意点: 不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。

useContext该函数接收一个 Context 类型的参数(就是包裹了 Provider 那个对象),返回 Provider 中的 value 属性对象的值。

React 组件允许 Consumers 订阅 context 的改变。而 Provider 就是发布这种状态的组件,该组件接收一个 value 属性传递给 Provider 的后代 Consumers。一个 Provider 可以联系到多个 Consumers。Providers 可以被嵌套以覆盖组件树内更深层次的值。

index.tsx:

import React, { useState, createContext } from 'react';
import Child from './index2';

export const CountText = createContext(4);

export default () => {
  const [price, setPrice] = useState(0)
  const [count, setCount] = useState(()=>{
    const price = 5
    return price
  })
  return (
    <div>
      <div>单价:{price}元</div>
      <button onClick={()=>setPrice(price+1)}>AddPrice</button>
      <div>数量:{count}个</div>
      <button onClick={()=>setCount(count+1)}>AddCount</button>
      <CountText.Provider value={count}>
        <Child Price={price}/>
      </CountText.Provider>
    </div>
  );
}

index2.tsx:

import React, { useContext } from 'react';
import { CountText } from './index'
export default props => {
  
  let count = useContext(CountText)
  return (
    <div>
      <div>
        这是从props拿到的单价:{props.Price}
      </div>
      <div>
        这是从CountText拿到的数量:{count}
      </div>
    </div>
  )
}

效果如下:除了useContext进行传值props依旧可以传值传回调。

3.useReducer使用方法

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

  • useReducer 接收三个参数,第一个参数为一个 reducer 函数,第二个参数是reducer的初始值,第三个参数为可选参数,值为一个函数,可以用来惰性提供初始状态。这意味着我们可以使用使用一个 init 函数来计算初始状态/值,而不是显式的提供值。如果初始值可能会不一样,这会很方便,最后会用计算的值来代替初始值。
  • reducer 接受两个参数一个是 state 另一个是 action ,用法原理和 redux 中的 reducer 一致。
  • useReducer 返回一个数组,数组中包含一个 state 和 dispath,state 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的函数。
import React, { useReducer } from 'react';
export default function ReducerDemo() {
  const [count, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        return state + 1;
      case 'sub':
        return state - 1;
      default:
        return state;
    }
  }, 1)
  function addNum() {
    dispatch({
      type: 'add'
    })
  }
  function subNum() {
    dispatch({
      type: 'sub'
    })
  }
  return (
    <div>
      <div>现在的数值是{count}</div>
      <button onClick={addNum}>加数值</button>
      <button onClick={subNum}>减数值</button>
    </div>
  )
}

同时,useReucer 也是 useState 的内部实现,useState 和 useReucer 的实现原理:

let memoizedState
function useReducer(reducer, initialArg, init) {
    let initState = void 0
    if (typeof init === 'function') {
        initState = init(initialArg)
    } else {
        initState = initialArg
    }
    function dispatch(action) {
        memoizedState = reducer(memoizedState, action)
    }
    memoizedState = memoizedState || initState
    return [memoizedState, dispatch]
}

function useState(initState) {
    return useReducer((oldState, newState) => {
        if (typeof newState === 'function') {
            return newState(oldState)
        }
        return newState
    }, initState)
}

4.useEffect

useEffect方法接收传入两个参数:

  • 回调函数:在第组件一次render和之后的每次update后运行,React保证在DOM已经更新完成之后才会运行回调。
  • 状态依赖(数组):当配置了状态依赖项后,只有检测到配置的状态变化时,才会调用回调函数。

注意:useEffect的第二个参数必须传空数组,这样它就等价于只在componentDidMount的时候执行。如果不传第二个参数的话,它就等价于componentDidMount和componentDidUpdate

useEffect(() => {
  //每次render后执行
  return () => {
  //当前 effect 之前对上一个 effect 进行清除
  }
})
useEffect(() => {
  //仅在第一次render后执行
  return () => {
  //组件卸载前执行
  }
}, [])
useEffect(() => {
  //arr变化后,render后执行
  return () => {
  //下一useEffect运行前执行
  }
}, arr)

 回调返回值

useEffect的第一个参数可以返回一个函数,当页面渲染了下一次更新的结果后,执行下一次useEffect之前,会调用这个函数。这个函数常常用来对上一次调用useEffect进行清理。

import React, { useState,useEffect } from 'react';

export default () => {
  const [price, setPrice] = useState(0)
  useEffect(() => {
    console.log('执行...', price);
    return () => {
      console.log('清理...', price);
    }
  }, [price]);
  return (
    <div>
      <div>单价:{price}元</div>
      <button onClick={()=>setPrice(price+1)}>AddPrice</button>
    </div>
  );
}

输出结果如图: useEffect中返回的是一个函数,这形成了一个闭包,这能保证我们上一次执行函数存储的变量不被销毁和污染。

清除副作用

const [value, setValue] = useState()
useEffect(() => {
    const timer1 = setTimeout(() => {
      console.log('send')
      console.log(value)
    }, 2000)
    return () => {
      console.log('clearLastSend')
      clearTimeout(timer1)
    }
  }, [value])
<Input value={value} placeholder="input here" onInput={e => setValue(e.target.value)} />

5.使用useRef获取dom的ref

import React, { useRef } from 'react';
export default () => {
  const inputRef = useRef(null);
  const onButtonClick = () => {
    console.log(inputRef.current.value)
  };
  return (
    <div>
      <input ref={inputRef} type='text'/>
      <button onClick={onButtonClick}>获取ref</button>
    </div>
  );
}

 

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页