
随着新的React 16.6.0的发布,HOOKS (建议)出现在文档中。 现在,它们在react 17.0.0-alpha中可用,并在开放的RFC:React Hooks中进行了讨论。 让我们看看它是什么,以及为什么需要削减它。
是的,它是RFC,您可以影响与react的创建者讨论的最终实现方式,为什么他们选择这种或那种方法。
让我们看一下标准挂钩的外观:
import { useState } from 'react'; function Example() {
尝试考虑一下这段代码,这是一个预告片,到本文结尾,您已经了解了它的含义。 您应该知道的第一件事是,这不会破坏向后兼容性,也许在RFC中收集反馈和建议后,它们会在16.7中添加。
就像伙计们保证的那样,这不是削减试剂种类的计划。
同样,钩子不能替代当前的反应概念,一切都可以代替props / state / context / refs。 这只是利用他们力量的另一种方式。
动机
钩子乍看之下解决了非连接问题,这些问题是在过去5年中来自Facebook的成千上万个组件的支持下出现的。
最困难的事情是在有状态组件中重用逻辑,反应无法将可重用行为附加到组件上(例如,将其连接到存储库)。 如果您使用过React,就会知道HOC(高阶组件)或渲染道具的概念。 这些足够好,但是有时它们使用过多,它们需要重组组件以便可以使用它们,这通常会使代码变得更麻烦。 值得研究典型的React应用程序,这将使问题变得清晰。

这称为包装地狱 -包装地狱。
在当前现实中,仅来自HOC的应用程序是正常的,它们将组件连接到商店/主题/本地化/自定义组件,我想每个人都知道这一点。
显然,反应需要另一种原始机制来分离逻辑。
使用挂钩,我们可以提取组件的状态,以便对其进行测试和重用。 挂钩允许您重用状态逻辑,而无需更改组件的层次结构。 这促进了许多组件或整个系统之间的链接交换。 同样,类组件看起来很吓人,我们描述了componentDidMount
/ shouldComponentUpdate
/ componentDidUpdate
的生命周期方法, componentDidMount
的状态,创建用于状态/边的方法,为组件实例绑定方法等,因此它可以不断地进行下去。 通常,此类组件超出了x行,其中x很难理解。
挂钩使您可以通过将组件之间的逻辑分解为小功能并在组件内部使用它们来执行相同的操作。
对人和汽车来说,上课都很困难
在观察Facebook类时,学习React是一个很大的障碍。 您需要了解this
工作原理,并且它不能像其他编程语言那样工作,您还应该记住绑定事件处理程序。 没有稳定的语法语句,代码看起来很冗长。 人们了解道具/状态模式和所谓的自上而下的数据流,但是这些类很难理解。
尤其是如果不限于模板,不久前反应中的人就使用Prepack 对组件的布局进行了试验,并看到了可喜的结果,但是尽管如此,该类的组件仍使您可以创建意想不到的不良模式,使这些优化消失,并且当热重载类使其不可靠。 首先,这些家伙想提供一个API,该API支持所有优化并且可以在热重启时正常工作。
看看钩子
状态钩
下面的代码呈现了一个段落和一个按钮,如果我们单击该按钮,则该段落中的值将增加。
import { useState } from 'react'; function Example() {
由此我们可以得出结论,该钩子与state
类的概念类似。
更详细一点的useState
方法使用一个参数,这是默认值,并返回一个元组,其中包含值本身以及用于更改它的方法,与setState不同,setCount不会合并值,而只是更新它。 我们还可以使用多个状态声明,例如:
function ExampleWithManyStates() {
因此,我们可以一次创建多个状态,而无需考虑如何以某种方式分解它们。 因此,可以区分出挂钩是允许您“连接”到类组件的芯片的函数,就像挂钩在类内部不起作用一样,记住这一点很重要。
效果钩
通常在类组件中,我们会产生副作用功能,例如,订阅事件或请求数据,为此,通常使用componentDidMount
/ componentDidUpdate
方法
import { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0);
当我们调用useEffect
我们告诉useEffect
在更新DOM树中的更改后做出“副作用”。 效果在组件内部声明,因此可以访问道具/状态。 此外,我们可以按照您希望的方式完全相同地创建它们。
function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); function handleStatusChange(status) { setIsOnline(status.isOnline); }
立即值得关注其中的第二个副作用,我们返回该函数,我们执行此操作是为了在组件执行卸载后执行一些操作,在新的api中,这称为带有清洗的效果。 其他效果可以返回任何结果。
挂钩规则
挂钩只是javascript函数,但它们只需要两个规则:
- 挂钩应在函数层次结构的最顶层执行(这意味着您不应在条件和循环中调用挂钩,否则反应不能保证挂钩的执行顺序)
- 仅在React函数或功能组件中调用钩子,或者在自定义钩子中调用钩子(如下所示)。
为了遵循这些规则,react团队的成员创建了一个linter插件 ,如果您在类组件或循环和条件中调用钩子,则该插件将引发错误。
定制挂钩
同时,我们想重用有状态组件的逻辑,为此通常使用HOC或渲染道具模式,但它们会增加应用程序的体积。
例如,我们描述以下功能:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
实现此代码,它将是一个自定义挂钩,我们可以在各个组件中调用它。 例如,像这样:
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
大概
function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
无论如何,我们都将重用组件的状态,每次对useFriendStatus
函数的调用useFriendStatus
创建一个隔离状态。 还值得注意的是,此函数的开头以单词use开头,这意味着它是一个钩子。 我们建议您遵循这种格式。 您可以为任何内容,动画/订阅/计时器等编写自定义钩子。
还有更多的挂钩。
useContext
useContext
允许您使用通常的返回值而不是renderProps,我们要在其中检索上下文并将其返回给我们,因此我们可以摆脱将上下文传递给prop的所有HOC。
function Example() { const locale = useContext(LocaleContext); const theme = useContext(ThemeContext);
现在我们可以在返回值中使用上下文对象。
useCallback
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
为了保存对方法的引用,您必须多久创建一个类的组件? 不再需要这样做,我们可以使用useCallback,并且不会重绘我们的组件,因为已经有了指向onClick的新链接。
useMemo
我们返回记忆值,记忆值意味着仅当其中一个参数已更改时才计算该值,而第二次将不计算该值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
是的,在这里您必须复制数组中的值,以便挂钩了解它们没有更改。
useRef
useRef
返回一个突变值,其中.current
字段将使用第一个参数初始化,只要组件存在,该对象就会存在。
专注于输入的最常见示例
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => {
useImperativeMethods
useImperativeMethods
定制从父级传递并直接使用ref的实例的值。 与往常一样,应避免直接链接, forwardRef
应使用forwardRef
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeMethods(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
在此示例中, FancyInput
的组件可以调用fancyInputRef.current.focus()
。
useMutationEffect
useMutationEffect
与useMutationEffect
非常相似, useEffect
处在于,它在更新相邻组件之前反应更改DOM值的阶段同步启动,此钩子应用于执行DOM突变。
最好使用useEffect来阻止视觉变化。
useLayoutEffect
useLayoutEffect
与useLayoutEffect
相似, useEffect
处在于,它在所有DOM更新和同步重新呈现之后均同步运行。 在浏览器可以绘制元素之前,将同步应用useLayoutEffect
中计划的更新。 您还应该尝试使用标准的useEffect
以免阻止视觉变化。
useReducer
useReducer
是用于创建返回状态和调度更改功能的reducer的钩子:
const [state, dispatch] = useReducer(reducer, initialState);
如果您了解Redux的工作原理,那么您将了解useReducer
工作原理。 与上述计数器相同的示例仅通过useReducer
:
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return initialState; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
UseReducer也带有3个参数,这是初始化reducer时应执行的action
:
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return {count: action.payload}; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer( reducer, initialState, {type: 'reset', payload: initialCount}, ); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
我们还可以在该化useContext
器中创建一个上下文,并通过useContext
钩子使用它来在整个应用程序中使用它,这仍然是家庭作业。
总结一下
挂钩是解决包装器地狱和解决若干问题的一种非常有效的方法,但是所有这些都可以与链接传输的相同定义一起使用。 现在已经开始出现使用钩子的 集合或该集合 。 您可以在文档中了解有关钩子的更多信息。