ReactJS:挂钩速查表





朋友们,美好的一天!



这是主要React挂钩的指南:useState,useEffect,useLayoutEffect,useContext,useReducer,useCallback,useMemo和UseRef。



灵感:React Hooks备忘单:解锁常见问题的解决方案



本指南的目的是简要概述每个挂钩的用途和功能。在对该钩子进行描述之后,有一个示例代码供您使用,并为您的实验提供了一个沙箱。



完整的钩子集在此存储库中可用



  1. 下载资料库
  2. 安装依赖项:npm i
  3. 运行:npm start


挂钩位于“挂钩”目录中。主文件是index.js。要执行特定的挂钩,您需要取消注释相应的导入和渲染线。



没有更多的前言。



useState



useState允许使用功能组件内部的变量状态。



可变状态


要确定变量的状态,请以初始状态作为参数调用useState:useState(initialValue)。



const DeclareState = () => {
  const [count] = useState(1);
  return <div>  - {count}.</div>;
};


更新变量的状态


要更新变量的状态,请调用useState返回的更新函数:const [state,updater] = useState(initialValue)。



代码:



const UpdateState = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  return (
    <>
      <p> {age} .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


沙箱:





多种状态


在一个功能组件中,您可以定义和更新多个变量的状态。



代码:



const MultStates = () => {
  const [age, setAge] = useState(19);
  const [num, setNum] = useState(1);

  const handleAge = () => setAge(age + 1);
  const handleNum = () => setNum(num + 1);

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={handleAge}> !</button>
      <button onClick={handleNum}>   !</button>
    </>
  );
};


沙箱:





使用对象确定变量的状态


除了字符串和数字外,还可以将对象用作初始值。请注意,useStateUpdater需要传递给整个对象,因为它将被替换而不是与上一个对象合并。



// setState ( ) - useState ( )
// ,    - {name: "Igor"}

setState({ age: 30 });
//   
// {name: "Igor", age: 30} -  

useStateUpdater({ age: 30 });
//   
// {age: 30} -   


代码:



const StateObject = () => {
  const [state, setState] = useState({ age: 19, num: 1 });
  const handleClick = (val) =>
    setState({
      ...state,
      [val]: state[val] + 1,
    });
  const { age, num } = state;

  return (
    <>
      <p> {age} .</p>
      <p>  {num}   .</p>
      <button onClick={() => handleClick('age')}> !</button>
      <button onClick={() => handleClick('num')}>   !</button>
    </>
  );


沙箱:





使用函数初始化变量的状态


变量状态的初始值可以通过函数确定。



const StateFun = () => {
  const [token] = useState(() => {
    const token = localStorage.getItem("token");
    return token || "default-token";
  });

  return <div> - {token}</div>;
};


用函数代替setState


useState返回的更新函数可以不仅仅是setState。



const [value, updateValue] = useState(0);
//    ,  ,  
updateValue(1);
updateValue((prevVal) => prevVal + 1);


第二种方法适用于更新取决于先前状态的情况。



代码:



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

  return (
    <>
      <p>   {count}.</p>
      <button onClick={() => setCount(0)}></button>
      <button onClick={() => setCount((prevVal) => prevVal + 1)}>
         (+)
      </button>
      <button onClick={() => setCount((prevVal) => prevVal - 1)}>
         (-)
      </button>
    </>
  );
};


沙箱:





useEffect



useEffect接受负责其他(副作用)功能的函数。



基本用途


代码:



const BasicEffect = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age + 1);

  useEffect(() => {
    document.title = ` ${age} !`;
  });

  return (
    <>
      <p>      .</p>
      <button onClick={handleClick}> !</button>
    </>
  );
};


沙箱:





消除(取消)效果


一种常见的做法是在一段时间后消除效果。可以使用传递给useEffect的效果返回的函数来完成此操作。以下是addEventListener的示例。



代码:



const CleanupEffect = () => {
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  return (
    <>
      <p>        .</p>
    </>
  );
};


沙箱:





多重效果


可以在功能组件中使用多次useEffects。



代码:



const MultEffects = () => {
  //   
  useEffect(() => {
    const clicked = () => console.log("!");
    window.addEventListener("click", clicked);

    return () => {
      window.removeEventListener("click", clicked);
    };
  }, []);

  //   
  useEffect(() => {
    console.log(" .");
  });

  return (
    <>
      <p>  .</p>
    </>
  );
};


沙箱:







请注意,可以通过向其传递一个空数组作为第二个参数来跳过对重新渲染时useEffect的调用。



效果依赖


代码:



const EffectDependency = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1)

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1)
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


沙箱:







在这种情况下,我们将randomInt依赖项传递给useEffect作为第二个参数,因此该函数将在初始渲染时以及randomInt更改时被调用。



跳过效果(空数组依赖)


在下面的示例中,useEffect传递了一个空数组作为依赖项,因此该效果仅在初始渲染时起作用。



代码:



const SkipEffect = () => {
  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, []);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


沙箱:







单击该按钮时,不会调用useEffect。



跳过效果(无依赖性)


在没有依赖项数组的情况下,每次呈现页面时都会触发效果。



useEffect(() => {
  console.log(
    "        ."
  );
});


useContext



useContext消除了依赖上下文使用者的需要。与MyContext.Consumer和props渲染相比,它具有更简单的界面。下面是使用useContext和Context.Consumer使用上下文的比较。



//    Context
const ThemeContext = React.createContext("dark")

//   
function Button() {
    return (
        <ThemeContext.Consumer>
            {theme => <button className={thene}> !</button>}
        </ThemeContext.Consumer>
}

//  useContext
import { useContext } from "react"

function ButtonHook() {
    const theme = useContext(ThemeContext)
    return <button className={theme}> !</button>
}


代码:



const ChangeTheme = () => {
  const [mode, setMode] = useState("light");

  const handleClick = () => {
    setMode(mode === "light" ? "dark" : "light");
  };

  const ThemeContext = React.createContext(mode);

  const theme = useContext(ThemeContext);

  return (
    <div
      style={{
        background: theme === "light" ? "#eee" : "#222",
        color: theme === "light" ? "#222" : "#eee",
        display: "grid",
        placeItems: "center",
        minWidth: "320px",
        minHeight: "320px",
        borderRadius: "4px",
      }}
    >
      <p> : {theme}.</p>
      <button onClick={handleClick}>  </button>
    </div>
  );
};


沙箱:





useLayoutEffect



useLayoutEffect 的行为与useEffect相似,但有一些例外,我们将在后面讨论。



  useLayoutEffect(() => {
    // 
  }, []);


基本用途


这是使用useEffect但使用useLayoutEffect的示例。



代码:



  const [randomInt, setRandomInt] = useState(0);
  const [effectLogs, setEffectLogs] = useState([]);
  const [count, setCount] = useState(1);

  useLayoutEffect(() => {
    setEffectLogs((prevEffectLogs) => [
      ...prevEffectLogs,
      `   ${count}.`,
    ]);
    setCount(count + 1);
  }, [randomInt]);

  return (
    <>
      <h3>{randomInt}</h3>
      <button onClick={() => setRandomInt(~~(Math.random() * 10))}>
           !
      </button>
      <ul>
        {effectLogs.map((effect, i) => (
          <li key={i}>{"  ".repeat(i) + effect}</li>
        ))}
      </ul>
    </>
  );
};


沙箱:





useLayoutEffect和useEffect


呈现页面后即调用传递给useEffect的函数,即 形成元素的布局和渲染之后。这适用于大多数不会阻塞流程的附加效果。但是,例如,如果要进行一些DOM操作作为附加效果,则useEffect不是最佳选择。为防止用户看到更改,应使用useLayoutEffect。呈现给页面之前,将调用传递给useLayoutEffect的函数。



useReducer



useReducer可以用作useState的替代方法,但是,当状态取决于先前的值或存在多个状态时,它的用途是封装用于处理状态的复杂逻辑。



基本用途


在下面的示例中,使用useReducer代替useState。useReducer调用返回状态值和调度函数。



代码:



const initialState = { width: 30 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const BasicReducer = () => {
  const [state, dispath] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


沙箱:





延迟(“惰性”)状态初始化


useReducer带有第三个可选参数,该函数返回一个状态对象。使用initialState作为第二个参数调用此函数。



代码:



const initializeState = () => ({
  width: 90,
});

//  ,  initializeState   
const initialState = { width: 0 };

const reducer = (state, action) => {
  switch (action) {
    case "plus":
      return { width: Math.min(state.width + 30, 600) };
    case "minus":
      return { width: Math.max(state.width - 30, 30) };
    default:
      throw new Error(" ?");
  }
};

const LazyState = () => {
  const [state, dispath] = useReducer(reducer, initialState, initializeState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => dispath("plus")}>
          .
      </button>
      <button onClick={() => dispath("minus")}>
          .
      </button>
    </>
  );
};


沙箱:





模拟this.setState的行为


useReducer使用的还原器比Redux严格。例如,传递给reducer的第二个参数不需要type属性。这为我们提供了有趣的机会。



代码:



const initialState = { width: 30 };

const reducer = (state, newState) => ({
  ...state,
  width: newState.width,
});

const NewState = () => {
  const [state, setState] = useReducer(reducer, initialState);
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <div
        style={{
          margin: "0 auto",
          background: color,
          height: "100px",
          width: state.width,
        }}
      ></div>
      <button onClick={() => setState({ width: 300 })}>
          .
      </button>
      <button onClick={() => setState({ width: 30 })}>
          .
      </button>
    </>
  );
};


沙箱:





useCallback



useCallback返回保存的(缓存的)回调。



入门模板


代码:



const CallbackTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


沙箱:







在上面的示例中,单击按钮时,“年龄”组件已更新并重新呈现。当新的回调传递到doSomething道具时,向导组件也会重新呈现。即使Guide使用React.memo来优化性能,它仍然会被重绘。我们该如何解决?



基本用途


代码:



const BasicCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";
  const doSomething = useCallback(() => someValue, [someValue]);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


沙箱:







内置useCallback


useCallback可以用作内置函数。



代码:



const InlineCallback = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = "some value";

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={useCallback(() => someValue, [someValue])} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


沙箱:





useMemo



useMemo返回存储的(缓存的)值。



入门模板


代码:



const MemoTemplate = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = { value: "some value" };
  const doSomething = () => someValue;

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


沙箱:





该模板与开始的useCallback模板相同,除了someValue是对象而不是字符串。尽管使用了React.memo,指南组件也被重新渲染。



但是为什么会这样呢?毕竟,对象是通过引用进行比较的,并且对someValue的引用随每次渲染而变化。有任何想法吗?



基本用途


doSomething返回的值可以使用useMemo存储。这样可以防止不必要的渲染。



代码:



const BasicMemo = () => {
  const [age, setAge] = useState(19);
  const handleClick = () => setAge(age < 100 ? age + 1 : age);
  const someValue = () => ({ value: "some value" });
  const doSomething = useMemo(() => someValue, []);

  return (
    <>
      <Age age={age} handleClick={handleClick} />
      <Guide doSomething={doSomething} />
    </>
  );
};

const Age = ({ age, handleClick }) => {
  return (
    <div>
      <p> {age} .</p>
      <p>   </p>
      <button onClick={handleClick}> !</button>
    </div>
  );
};

const Guide = React.memo((props) => {
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;
  return (
    <div style={{ background: color, padding: ".4rem" }}>
      <p style={{ color: color, filter: "invert()" }}>
           .
      </p>
    </div>
  );
});


沙箱:





useRef


useRef返回一个ref对象。该对象的值可通过“当前”属性获得。可以为该属性分配一个初始值:useRef(initialValue)。在组件的生存期内存在一个ref对象。



访问DOM


代码:



const DomAccess = () => {
  const textareaEl = useRef(null);
  const handleClick = () => {
    textareaEl.current.value =
      " - ,     . ,    !";
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
                 .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


沙箱:





类实例变量(泛型)


ref对象可以包含任何值,而不仅仅是指向DOM元素的指针。



代码:



const StringVal = () => {
  const textareaEl = useRef(null);
  const stringVal = useRef(
    " - ,     . ,    !"
  );
  const handleClick = () => {
    textareaEl.current.value = stringVal.current;
    textareaEl.current.focus();
  };
  const color = `#${((Math.random() * 0xfff) << 0).toString(16)}`;

  return (
    <>
      <button onClick={handleClick}>
         .
      </button>
      <label htmlFor="msg">
               .
      </label>
      <textarea ref={textareaEl} id="msg" />
    </>
  );
};


沙箱:





useRef可用于存储计时器标识符,以便以后停止。



代码:



const IntervalRef = () => {
  const [time, setTime] = useState(0);
  const setIntervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      setTime((time) => (time = new Date().toLocaleTimeString()));
    }, 1000);

    setIntervalRef.current = id;

    return () => clearInterval(setIntervalRef.current);
  }, [time]);

  return (
    <>
      <p> :</p>
      <time>{time}</time>
    </>
  );
};


沙箱:





希望您喜欢这篇文章。感谢您的关注。



All Articles