react hook 总结

build后可以看到每次就只执行一次了

使用条件

只能在顶层使用, 不能嵌套, 也不能在条件判断和循环中使用, 会直接报错

这是由于hook内部记录状态时使用的链表, 由于判断会打乱调用顺序, 循环不能保证每次调用的次数是一致的, 所以都是被禁止的

 

 

useState

在更新state时不会自动合并对象, 不像setState可以通过传递第二个参数来在状态更新之后做一些事情, 尽量使用多个小状态而不是一个大状态

简单计数器

import React, { useState } from "react";
import { inc, applyTo } from "ramda";
const B = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      {count} <button onClick={() => applyTo(inc, setCount)}>inc</button>
    </div>
  );
};

export default B;

useState 内部使用useReducer实现, 是一个简单的语法糖

import React, { useReducer } from "react";
import { inc } from "ramda";
const useState = (initState) => {
  return useReducer(
    (state, action) => (typeof action === "function" ? action(state) : action),
    typeof initState === "function" ? initState() : initState
  );
};
const B = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      {count} <button onClick={() => setCount(inc)}></button>
    </div>
  );
};

export default B;

 

useReducer

一个”比较”复杂的计数器

import React, { useReducer } from "react";
import { inc, dec, cond, equals, negate,always } from "ramda";
const reducer = (state, action) =>
  cond([
    [equals("inc"), always(inc(state))],
    [equals("dec"), always(dec(state))],
    [equals("negate"), always(negate(state))],
  ])(action);

const B = () => {
  const [count, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      {count}
      <button onClick={() => dispatch("inc")}>inc</button>
      <button onClick={() => dispatch("dec")}>dec</button>
      <button onClick={() => dispatch("negate")}>negate</button>
    </div>
  );
};

export default B;

 

 

useMemo

类似vue的computed, 但是依赖数组需要自己写

import React, { useState, useMemo } from "react";
import { multiply, inc } from "ramda";

const B = () => {
  const [count, setCount] = useState(0);
  const double = useMemo(() => multiply(2, count), [count]);
  return (
    <div>
      count:{count} double:{double}
      <button onClick={() => setCount(inc)}>inc</button>
    </div>
  );
};

export default B;

 

使用useMemo实现useRef和useCallback

useCallback 相当于useMemo返回一个函数, 

useRef通过传递一个空依赖数组, 保证返回的是一个引用不变对象

const useRef = (value) =>
  useMemo(
    () => ({
      current: value,
    }),
    []
  );

const useCallback = (cb, deps) => useMemo(() => cb, deps);

 

使用useRef实现useMemo

import React, { useRef, useState } from "react";
import { multiply,inc } from "ramda";
const useMemo = (getter, deps = []) => {
  const depsRef = useRef(deps);
  const valueRef = useRef(getter());
  if (deps && deps === depsRef.current) return valueRef.current;
  depsRef.current = deps;
  return (valueRef.current = getter());
};
const B = () => {
  const [count, setCount] = useState(0);
  const double = useMemo(() => multiply(2, count), [count]);
  return (
    <div>
      count:{count} double:{double}
      <button onClick={() => setCount(inc)}>inc</button>
    </div>
  );
};

export default B;

 

useCallback

useMemo的语法糖, 但是有很多小问题

经典的由于闭包导致异步函数拿不到最新的值, 此时需要使用useRef保存最新值

import React, { useState } from "react";
import { inc, applyTo, curry } from "ramda";

const B = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(inc);
    setTimeout(() => {
      console.log(count);
    });
  };

  return (
    <div>
      {count} <button onClick={handleClick}>inc</button>
    </div>
  );
};

export default B;

 

useEffect 和 useLayoutEffect

两者的执行时机不一样

useEffect在已经渲染完毕后执行

useLayoutEffect在dom挂载完毕, 渲染之前执行

下面这段代码, 会改变两个div中的文字, 第一个会出现闪烁, 因为先渲染了短文字, 后渲染了长文字, 第二个不会出现闪烁, 因为短文字并没有被渲染, 也就没有了视觉误差

import React, { useEffect, useLayoutEffect } from "react";

const useMount = (effect) => {
  return useEffect(effect, []);
};

const useBeforeMount = (effect) => {
  return useLayoutEffect(effect, []);
};
const B = () => {
  useMount(() => {
    document.getElementById("app1").innerText = "hello";
  });
  useBeforeMount(() => {
    document.getElementById("app2").innerText = "hello";
  });
  return (
    <div>
      <div id="app1">B</div> <br/> <div id="app2">B</div>
    </div>
  );
};

export default B;

 

 

useRef

用于保存无关渲染的状态, 改变ref的值并不会触发渲染

import React, { useState, useRef } from "react";
import { inc, applyTo, curry } from "ramda";

const B = () => {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;
  const handleClick = () => {
    setCount(inc);
    setTimeout(() => {
      console.log(count, countRef.current);
    });
  };

  return (
    <div>
      {count} <button onClick={handleClick}>inc</button>
    </div>
  );
};

export default B;

useContext

当你准备使用这个hook时其实你需要的是redux…. 

 

useImperativeHandle 和 forwardRef

用于暴露指定的接口, ref可以直接暴露dom, 这给了父组件太大的权限, 使其可以任意操作dom, 但是我们也可以只暴露一个操作方法, 比如focus, 父组件只能通过调用这个方法来操作子组件

import React, {
  useState,
  useRef,
  useEffect,
  forwardRef,
  useImperativeHandle,
} from "react";
import { inc, dec } from "ramda";
const Count = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  return <div ref={ref}>{count}</div>;
});

const CountLess = forwardRef((props, ref) => {
  const divRef = useRef(null);
  const [count, setCount] = useState(0);

  useImperativeHandle(ref, () => ({
    inc: ()=>setCount(inc),
    dec: ()=>setCount(dec),
  }));
  return <div ref={divRef}>{count}</div>;
});
const B = () => {
  const countRef = useRef(null);
  const countLessRef = useRef(null);
  const incHandler = () => {
    console.log("inc");
    console.log(countRef.current, countLessRef.current);
    // console.log(countRef.current.inc, countLessRef.current.inc);
    countLessRef.current.inc()
  };
  const decHandler = () => {
    console.log("dec");
    console.log(countRef.current, countLessRef.current);
    // console.log(countRef.current.inc, countLessRef.current.inc);
    countLessRef.current.dec()
  };
  return (
    <div>
      <Count ref={countRef}></Count>
      <CountLess ref={countLessRef}></CountLess>
      <button onClick={incHandler}>inc</button>
      <button onClick={decHandler}>dec</button>
    </div>
  );
};

export default B;

 

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » react hook 总结