React Hook - Hook规则
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Hook - Hook规则相关的知识,希望对你有一定的参考价值。
参考技术AHook 本质其实就是函数,但是我们在使用他们的时候需要遵守两条规则:
eslint-plugin-react-hooks 这个 ESLint 插件来强制执行这两条规则。
我们可以在单个组件中使用多个 State Hook 或 Effect Hook
那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。
因为我们的示例中,Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作:
只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但如果我们将一个 Hook (例如 persistForm effect) 调用放到一个条件语句中会发生什么呢?
在第一次渲染中 name !== \'\' 这个条件值为 true,所以我们会执行这个 Hook。但是下一次渲染时我们可能清空了表单,表达式值变为 false。此时的渲染会跳过该 Hook,Hook 的调用顺序发生了改变:
React 不知道第二个 useState 的 Hook 应该返回什么。React 会以为在该组件中第二个 Hook 的调用像上次的渲染一样,对应的是 persistForm 的 effect,但并非如此。从这里开始,后面的 Hook 调用都被提前执行,导致 bug 的产生。
这就是为什么 Hook 需要在我们组件的最顶层调用。 如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部:
原因是:React 靠的是 Hook 调用的顺序,来寻找每次组件调用时变量的对应关系。
React Hook要点笔记
文章目录
hook 是有状态的函数.
使用规则
- 只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook
- 只在 React 函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook
state hook
和class component相比,state hook采用的是替换而非合并,因此可将state逐一声明.
useReducer和useContext
对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。
context
Context的作用就是对它所包含的组件树提供全局共享数据的一种技术
useContext以Hook的方式使用React Context。
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
// 第一步:创建需要共享的context
const ThemeContext = React.createContext('light');
class App extends React.Component
render()
// 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props)
return (
<div>
<ThemedButton />
</div>
);
function ThemedButton(props)
// 第三步:使用共享 Context
const theme = useContext(ThemeContext);
render()
return <Button theme=theme />;
子孙类组件出发reducer状态变化。就是将dispatch函数作为context的value,共享给页面的子组件。
// 定义初始化值
const initState =
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
// 定义state[业务]处理逻辑 reducer函数
function loginReducer(state, action)
switch(action.type)
case 'login':
return
...state,
isLoading: true,
error: '',
case 'success':
return
...state,
isLoggedIn: true,
isLoading: false,
case 'error':
return
...state,
error: action.payload.error,
name: '',
pwd: '',
isLoading: false,
default:
return state;
// 定义 context函数
const LoginContext = React.createContext();
function LoginPage()
const [state, dispatch] = useReducer(loginReducer, initState);
const name, pwd, isLoading, error, isLoggedIn = state;
const login = (event) =>
event.preventDefault();
dispatch( type: 'login' );
login( name, pwd )
.then(() =>
dispatch( type: 'success' );
)
.catch((error) =>
dispatch(
type: 'error'
payload: error: error.message
);
);
// 利用 context 共享dispatch
return (
<LoginContext.Provider value=dispatch>
<...>
<LoginButton />
</LoginContext.Provider>
)
function LoginButton()
// 子组件中直接通过context拿到dispatch,出发reducer操作state
const dispatch = useContext(LoginContext);
const click = () =>
if (error)
// 子组件可以直接 dispatch action
dispatch(
type: 'error'
payload: error: error.message
);
参考:
effect hook
可以把 useEffect Hook
看做componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
通常我们都会在第一次dom渲染完成以及后续dom重新更新时,去调用我们的副作用操作。
Effect在默认情况下,会在第一次渲染之后和每次更新之后都会执行,这也就让我们不需要再去考虑是componentDidMount
还是componentDidUpdate
时执行,只需要明白Effect在组件渲染后执行即可.
可以使用Effect来清除这些副作用,只需要在Effect中返回一个函数即可:
seEffect(() =>
pollingNewStatus()
//告诉React在每次渲染之前都先执行cleanup()
return function cleanup()
unPollingNewStatus()
;
);
有个明显的区别在于useEffect其实是每次渲染之前都会去执行cleanup(),而componentWillUnmount
只会执行一次。
在Effect中,我们可以通过增加Effect的第二个参数即可,如果没有变化,则跳过更新:
useEffect(() =>
document.title = `You clicked $count times`;
, [count]); // 仅在 count 更改时更新
useCallback
把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。
通过useCallback获得一个记忆后的函数。第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback就会重新返回一个新的记忆函数提供给后面进行渲染。如果依赖项没有变,那么记忆函数的引用就不会变,当函数作为props传递给子组件时,就不会引发子组件的重新渲染:
function App()
const memoizedHandleClick = useCallback(() =>
console.log('Click happened')
, []); // 空数组代表无论什么情况下该函数都不会发生改变
return <SomeComponent onClick=memoizedHandleClick>Click Me</SomeComponent>;
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。
useMemo
useCallback 的功能完全可以由useMemo所取代.
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
唯一的区别是:useCallback不会执行第一个参数函数,而是将它返回给你,而useMemo会执行第一个函数并且将函数执行结果返回给你。
当useCallback依赖项没有改变时,返回缓存的函数,否则返回一个新的函数。
而useMemo更适合经过函数计算得到一个确定的值,比如记忆组件或者计算的值,在依赖项没变时返回缓存的值。
function Parent( a, b )
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a=a />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b=b />, [b]);
return (
<>
child1
child2
</>
)
只有依赖项a/b发生改变时,才会触发相应子组件的重新渲染。
useRef 保存引用值
可以保存对dom/组件的引用,此外可以很方便地保存任何可变值,其类似于在class中使用实例属性。在组件生命周期的每次渲染时返回同一个ref对象,而不像state返回一个新的。可以用来跨越渲染周期存储数据(即多次渲染仍然是返回同一个引用对象)。
常见的用来存储定时器。
写入它会被视为“副作用”,因此咱们无法在渲染过程中更改它,需要在useEffect hook 中才能修改。
变更 .current 属性不会引发组件重新渲染。
useLayoutEffect
useEffect传入的回调是异步的。useLayoutEffect中的副作用会在DOM更新之后立马同步执行,和原来componentDidMount&componentDidUpdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。
自定义hook
通过自定义hook来封装组件要共享的业务逻辑。自定义hook就是以use开头且调用其他hook的函数。
自定义的hook强大之处在于拥有状态,当状态值改变时,它可以触发调用组件的重新渲染,而且自定义hook可以跟随着组件的生命周期,在不同的生命钩子阶段,我们可以处理一些事件。
UI组件只需要去消费hook产出的value和function。状态处理都封装在了自定义hook里。只要符合它们的接口格式约定,UI组件就可以随时随地地复用这些逻辑。
自定义hook的设计范式:
const state, handleChange, others = useCustomHook(config, dependency?);
- config声明了hook所需要的数据,可能是内部useState的初始值,也可能是结构化的数据,总结起来就是这个hook的配置
- dependency通常只有hook内使用了useEffect、useCallback这类API,需要我们声明依赖的时候需要传入。
参考:
- React Hook 不完全指南
- 十个案例学会 React Hooks
- React Hook快速入门
- 函数式组件 && React Hook
- 用 useContext + useReducer 替代 redux
以上是关于React Hook - Hook规则的主要内容,如果未能解决你的问题,请参考以下文章