react初学者对于 hooks 的理解

447 阅读5分钟

react初学者对于 hooks 的理解

useState

让我们函数组件中也拥有 satate,这样我们就可以永远不用 class 组件啦

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);
	// 第一个参数是你的数据
  // 第二个参数是更新数据的方法
  // 右侧useState()中的参数是你的数据的默认数据,可以是复杂对象
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}> // 使用 setCount 更新数据
        Click me
      </button>
    </div>
  );
}
  1. 如果 state 是一个复杂对象,能否部分更新 setState?

不能,你可以使用...拓展运算符把之前的数据混入

function  Example (){
    const [state, setState] = useState({
      name:'bibi',
      age:18
    });
  return (
    <div>
      <p>我的名字是{state.name}</p>
      <p>我今年{state. age}岁了</p>
      <button onClick={() => setState({...state,name:'fineley'})}> 
        改名
      </button>
    </div>
  );
}
  1. 如果setState(obj)的内存地址没有变化,那么 React 就认为数据没有变化
function  Example () {
    const [state, setState] = useState({
      name:'bibi',
      age:18
    });
  	const onClick = () => {
  	sate.name = 'fineley'
      setState(state) // 无效!!! 因为你的地址没有变化,请生成一个新的对象
    }
  return (
    <div>
      <p>我的名字是{state.name}</p>
      <p>我今年{state. age}岁了</p>
      <button onClick={onClick}> 
        改名
      </button>
    </div>
  );
}
  1. useState 的第一个参数可以是一个函数

    function  Example (){
        const [state, setState] = useState(()=>({
          name:'bibi',
          age:9+9
        })); 
     	// ...
    }
    

    有什么区别呢?

    可以生成一些复杂的对象,在非初次执行时会少计算一次数据(没啥卵用)

  2. setState 可以接受函数

    import React, { useState } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);]
     	const onClick = ()=>{
        setCount(n+1)
      	setCount(n+1)  // 只会执行会后一次
        // 正确使用
        setCount(x=>x+1) // x 是回调的占位符可以是任何参数
        setCount(x=>x+1) // 将会按预期执行 
      }
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={onClick}> // 使用 setCount 更新数据
            Click me
          </button>
        </div>
      );
    }
    

useEffect

Effect Hook 可以让你在函数组件中执行副作用操作,官方文档的描述我一直没看懂,不一定是执行副作用,副作用也不一定在 useEffect中执行

我对于useEffect的理解是叫做 afterRender,每次 render 之后执行

  1. 作为 componentDidMount 使用,[]作为第二个参数,没有依赖任何参数,用于初始化操作,请求数据等操作
  2. 不要试图去欺骗 react.请把所有的依赖卸载第二个参数中,否则会导致意想不到的结果
  3. 每次渲染都执行执行
  4. 只在依赖变化的时候执行(有点类似 vue 的 watch)
  5. 当组件卸载时候 return 一个 fn 里面写上执行的操作
  6. 使用多个useEffect顺序是从上到下执行
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('bibi');
  useEffect(() => {
   	 console.log('我出生了')
  },[]); //1. 只执行一次(空数组里面的变量变化时执行)
  
  useEffect(() => {
   	 console.log('我出生了123456')
  }); //2. 每次都执行(任何一个状态变化都会执行)
  
   useEffect(() => {
   	 console.log('name 变了我就变')
  },[name]); //3. 第一此和依赖变化都会调用
    
  return (
    <div>
      <p>{name}</P>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      // ... fn changename
    </div>
  );
}

useLayoutEffect

  • useLayoutEffect会在 dom 生成完成之后,render 之前调用,而useEffect 会在 render 之后调用(useLayoutEffect在浏览器渲染之前执行,useEffect 在浏览器渲染之后执行)

  • 当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制

image.png

  • useLayoutEffect 总是比 useEffect 先执行
  • 使用useLayoutEffect中最好改变了 layout
  • 优先使用 useEffect(优先渲染)

useReducer

useReaducer 是 useState 的升级版,含有复杂逻辑可以适用 useReaducer,用于减少复杂度

const initialState = {count: 0}; // 创建初始值

const  reducer = (state, action)=> { // state:之前的数据,action:动作
  switch (action.type) {
    case 'add':
      return {count: state.count + action.number};
    case 'desc':
      return {count: state.count - action.number};
    default:
      throw new Error('unknown type');
  }
}

const Counter() => {
  const [state, dispatch] = useReducer(reducer, initialState); // 初始化
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'desc', number: 1})}>-</button>
      <button onClick={() => dispatch({type: 'add', number: 1})}>+</button>
    </>
  );
}

用 useReducer 结合 useContext 实现 Redux

  1. 将数据集中在一个 store 对象
  2. 将所有操作集中再 reducer
  3. 创建一个 context
  4. 创建对数据的读写 api
  5. 将第四步的读写 api 放到第三步的 context 中
  6. 用 context.provider 将 context提供给所有组件
  7. 各个组件用 usecontext 获取读写 api

useMemo

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算,用于优化性能

  • 父元素每次渲染会重新渲染子元素,可以适用 useMemo 让当只有依赖项变化时才重新渲染.用 React.memo 包一层,当 props 不变时,不会重新 render,当 props 里面有函数的时候,函数的地址会发生变化,react看来 props 还是发生了变化,props 中有函数的时候需要把函数包一层 useMemo 才能实现想要的结果

  • 用于缓存函数,结合 React.memo 用于优化性能,减少多余的 render

  • useMemo 接受一个函数,函数的返回值就是你需要的缓存的函数/对象等

  • 十分像 vue 的 computed(缓存/计算/返回值)

      const memoizedValue = useMemo(() => {
        return ()=>{}
      },[a]); // 依赖
    

useCallback

如果你觉得 useMeno 写的很别扭

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

  const memoizedValue = useMemo(()=>{},[a]); // 依赖

useRef

如果你希望有一个值在 render 过程中保持不变(地址),请适用 useref

  1. 使用了 current 才能保证两次渲染是同一个值(只有引用能做到)
  2. 在 fn 组件中如果要传递 ref 需要用 React.forwardRef 包一层,作为组件的第二个参数传递
const ref = React.useRef(null)
<FancyButton ref={ref}>Click me!</FancyButton>;

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref}>
    {props.children}
  </button>
));

useImperativeHandle

  1. useImperativeHandle可以自定义 ref 传递给父组件使用
const FancyInput = (props, ref)=>{
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

自定义 hook

  1. 用于将组件逻辑提取到可重用的函数中