Hook 详解(一)

在不编写 class 的情况下使用 state(私有状态) 以及其他的 React 特性(生命周期等)。

Hook规则

  • 在React的函数组件中调用 Hook
  • 在自定义Hook中调用其他 Hook

Hooks的串联是一个链式的数据结构,从根节点向下通过next进行串联。因此Hooks不能嵌套使用,不能在条件判断中使用,也不能在循环中使用,否则会破坏链式结构,一旦破坏,就会读写混乱。

Hook源码

useState

  • 定义值
  • 定义对象
  • 定义数组
  • 定义函数
import React, { useState } from 'react';

function Index() {
  // 第一个值用于读取状态,第二个值用于修改状态
  const [count, setCount] = useState(0);
  const [obj, setObj] = useState({name: '张三'});
  const [arr, setArr] = useState([1,2,3]);
  const [func, setFunc] = useState(() => {
      return count;
  });
  
  return (
    <div className={styles.demo}>
      <div>
        <h2>You clicked {count} times</h2>
        <button onClick={() => setCount(count + 1)}>
            改变值
        </button>
      </div>
      <div>
        <h2>{obj.name}</h2>
        <button onClick={() => setObj({name: 'lisi'})}>改变对象</button>
      </div>
      <div>
        <h2>{arr}</h2>
        <button onClick={() => setArr(() => {
            arr.push(4);
            return [...arr];
        })}>改变数组</button>
      </div>
      <div>
        <h2>{func}</h2>
        <button onClick={() => setFunc(count + 1)}>
            改变函数
        </button>
      </div>
    </div>
  );
}
使用注意点:
  • 不支持局部更新
const [myState, setMyState] = React.useState({
  name: 'guangyu',
  age: 18
})

setMyState({ age: 19 }) // 其他属性丢失
setMyState({ ...myState, age: 19 }) // 新的 state 合并了旧属性
  • 当需要改变值时,必须通过setState修改,或者深拷贝一个新的数组进行更改在赋值
const [todoLists, getLists] = useState([]);
const list = JSON.parse(JSON.stringify(todoLists));
list.map(item => {
          if (item.title === info.title) {
              item.isEdit = state;
          } else {
              item.isEdit = false;
          }
          return list;
      });
getLists(list);

useEffect

  • 相当于react中 componentDidMountcomponentDidUpdatecomponentWillUnmount 三个生命周期

  • 副作用(DOM操作, 数据请求,组件更新)

  • useEffect为什么在组件函数内部执行?可以获取props和state,采用闭包的形式

  • 无阻塞更新

  • useEffect(回调函数,数组(可不写))

  • 多个useEffect()

Q: 为什么在组件挂载之后请求数据?

A: 挂载前,当数据未请求回来或报错,会阻碍页面的渲染,组件挂载之后请求能实现无阻塞更新。

当第二个参数未填时,会监听所有的状态
  useEffect(() => {
      console.log(count);
  });
当第二个参数为空数组时,不监听任何状态。只在页面初始时,监听
 useEffect(() => {
      console.log(count);
  }, []);
当第三个参数是某一个状态时,只监听该状态,其余不监听,实现了componentDidUpdate
useEffect(() => {
      console.log(count);
  }, [count]);
实现 componentWillUnmount
 useEffect(() => {
      console.log(count);
      // 通过函数
      return () => {
          console.log('componentWillUnmont'); // 每次都先销毁之前的
      };
  }, [count]);

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

  • 访问DOM,获取DOM元素
  • 保存变量
const inputEl = useRef('123'); // 获取Dom
const save = useRef({value: '111'}); // 保存变量
<div className={styles.ref}>
    <h1>useRef</h1>
    <Input 
      type="text"
      fullWidth
      ref={inputEl}
    />
		<br />
    <Button type="primary" onClick={() => {
        // console.log('input', inputEl.current.state.value); // 获取DOM
        save.current.value = inputEl.current.state.value  // 保存变量
        console.log('save', save);
    }}>获取ref</Button>
</div>

useContext

使用上下文,让组件之间也能有局部的全局变量。(相当于有了this)

  • useContextcreateContext (生成一个父组件容器)一起使用

  • 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。

  • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

  • 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。(即使祖先使用 React.memoshouldComponentUpdate

  • useContext 的参数必须是 context 对象本身

    • 正确: useContext(MyContext)
    • 错误: useContext(MyContext.Consumer)
    • 错误: useContext(MyContext.Provider)
// 父组件
  const MyContext = createContext();
// 子组件
  const ChildContext = () => {
      const count = useContext(MyContext);
      return (
          <h2>我是子组件{count}</h2>   
      );
  };
  
{/* 父子传值, value为父组件准备传给子组件的值 */}
<MyContext.Provider value={count}>
	  <ChildContext />
</MyContext.Provider>

useMemo

  • shouldComponentUpdate 类似作用,在渲染过程中避免重复渲染的问题(提升性能)
  • 如果没有提供依赖项数组,useMemo() 在每次渲染时都会计算新的值。
  • 当状态或父组件传来的属性发生变化时,更新组件 (也可在子组件中控制状态是否更新)
  • useMomo() 是一个函数,有两个参数,第一个参数是函数,第二个参数是数组(可不写)
  • useMomouseEffect 执行的时间不同,useEffect 是在 componentDidMount 以后执行的,而 useMemo 是在组件渲染过程中执行的

useMomo 就是用的 memoization 来提高性能的, memoizationJavaScript 中的一种缓存技术。

如果我们有CPU密集型操作,我们可以通过将初始操作的结果存储在缓存中来优化使用。如果操作必然会再次执行,我们将不再麻烦再次使用问的CPU,因为相同结果的结果存储在某个地方,我们只是简单的返回结果。

因此,这个是以空间换速度。使用前需要确定是否值得这么做。

当第二个参数不传时,会监听所有状态并更新
 const res = useMemo(() => {
      return count;
  });
当第二个参数为空数组时,不管状态变不变,都不更新
 const res = useMemo(() => {
      return count;
  }, []);
当第三个参数是某一个状态时,只更新该状态,其余不更新
const [count, setCount] = useState(0);
const [num, setNum] = useState(1);
  
const res = useMemo(() => {
      return {
          count, num
      };
}, [count]);
  
  <div className={styles.memo}>
      <h1>useMemo</h1>
      <h2>状态=count{res.count} --- {res.num}</h2>
      <Button onClick={() => {
      		setCount(count + 1);
      }}>change-count</Button>
      <Button onClick={() => {
      		setNum(num + 1);
      }}>change-num</Button>
  </div>

虽然点击num 不更新,但在此点击count时,num的值会变成之前点击的值,这是因为num的值虽然没更新,但发生了变化,并保存在缓存中了。

若想分别控制并更新,可以分开写,并分别传入需要控制的状态数组

useCallback

  • 作用: 避免组件重复渲染,提高性能(和useMemo一致)
  • 可以控制组件什么时候需要更新
  • 虽同样使用到缓存技术,但和 useMemo 不同的是,useCallback 缓存的是个函数,是个函数就可以执行
  • useCallback() 有两个参数,第一个参数是函数,第二个参数是数组(可以不写)
const [count, setCount] = useState(0);
const [num, setNum] = useState(1);

const callBack = useCallback(() => {
    console.log('count', count);
    return count;
}, [count]);

<div className={styles.callback}>
    <h1>useCallback</h1>
    <h2>callBack: {callBack()}</h2>
    <h2>count状态=count: {count}</h2>
    <h2>num状态=num: {num}</h2>
    
    <Button onClick={() => {
    		setCount(count + 1);
    }}>change-count</Button>
    
    <Button onClick={() => {
    		setNum(num + 1);
    }}>change-num</Button>
</div>
原文地址:https://www.cnblogs.com/zpsakura/p/14393462.html