朋友们,美好的一天!
这是主要React挂钩的指南:useState,useEffect,useLayoutEffect,useContext,useReducer,useCallback,useMemo和UseRef。
灵感:React Hooks备忘单:解锁常见问题的解决方案。
本指南的目的是简要概述每个挂钩的用途和功能。在对该钩子进行描述之后,有一个示例代码供您使用,并为您的实验提供了一个沙箱。
完整的钩子集在此存储库中可用。
- 下载资料库
- 安装依赖项:npm i
- 运行: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>
</>
);
};
沙箱:
希望您喜欢这篇文章。感谢您的关注。