在生产中使用React钩子的5个最佳实践

这篇文章的作者(我们今天要翻译的译本)说,commercetools在2019年初-当它们出现在React 16.8.0中时就采用了React钩子从那时起,该公司的程序员一直在不断处理他们的代码,并将其转换为钩子。React挂钩允许您使用组件状态和使用其他React功能,而无需使用类。使用挂钩,您可以在使用功能组件时“连接”到组件生命周期的事件并响应其状态的变化。



简要介绍实现钩子的结果


简而言之,如果要讨论这些钩子给我们带来了什么,那么事实证明,它们通过从组件中提取逻辑并促进不同功能的组合来帮助简化了代码库。而且,使用钩子已经使我们学到了很多东西。例如,如何通过不断改进现有功能来更好地构建代码。我们确信,在继续使用React钩子的同时,我们将学到更多有趣的东西。

挂钩实现的优势


这是钩子的介绍给我们的:

  • 提高代码的可读性。由于使用了比以前更小的组件树,因此这是可能的。我们希望放弃渲染属性和更高阶的组件,从而促进了此类组件树的创建。
  • 改进代码调试功能。我们可以使用React开发人员工具提供的改进的可视化代码表示形式和其他调试信息。
  • 改善项目的模块化。由于挂钩的功能特性,简化了创建和应用适合重复使用的逻辑的过程。
  • 职责分离。React组件负责接口元素的外观,并且钩子提供对封装程序逻辑的访问。

现在,我们要分享有关在生产中使用钩子的建议。

1.及时从组件中检索钩子并创建自定义钩子


开始在功能组件中使用挂钩非常容易。例如,我们迅速在某处React.useState某处应用- React.useEffect并继续做我们的工作。但是,这种方法不能充分利用钩子能够提供的所有机会。呈现React.useState在一个小的形式use<StateName>State,和React.useEffect-的形式use<EffectName>Effect,我们能够实现以下目标:

  1. 组件比常规钩子小。
  2. 挂钩可以在各种组件中重复使用。
  3. , React, , . , State<StateName> State. React.useState, .

检索挂钩还有助于在应用程序的各个部分中更容易看到可重用和共享的逻辑。如果仅使用组件代码中内置的挂钩,则很难注意到类似或重复的逻辑。产生的钩子可能很小,并且包含很少的类似代码的代码useToggleState。另一方面,更大的钩子(如useProductFetcher)现在可以包含更多功能。这些都通过减小React组件的大小帮助我们简化了代码库。

下面的示例创建一个小的React钩子,用于控制元素的选择。在意识到应用程序中经常使用这种逻辑后,立即封装这种功能的好处就显而易见了。例如,从列表中选择一组订单。

//  

function Component() {
    const [selected, setSelected] = React.useState()
    const select = React.useCallback(id => setSelected(/** ... */), [
        selected,
        setSelect
    ])

    return <List selectedIds={selected} onSelect={id => select(id)} />
}

//   

const useSelection = () => {
    const [selected, setSelected] = React.useState()
    const select = React.useCallback(id => setSelected(/** ... */), [
        selected,
        setSelect
    ])

    return [selected, select]
}

function Component() {
    const [selected, select] = useSelection()

    return <List selectedIds={selected} onSelect={id => select(id)} />
}

2.关于React.useDebugValue的好处


标准的React.useDebugValue钩子引用鲜为人知的React功能。这个钩子可以在代码调试期间为开发人员提供帮助;在设计用于共享的钩子中使用它时很有用。这些是在应用程序的许多组件中使用的用户挂钩。但是,useDebugValue不建议在所有用户挂钩中都使用它,因为内置挂钩已经记录了标准调试值。

想象一下创建一个自定义钩子,该钩子旨在决定是否需要启用应用程序的某些功能。决策所基于的应用程序状态数据可以来自不同的来源。它们可以存储在React上下文对象中,可以通过访问该对象React.useContext

为了帮助开发人员进行调试,同时使用React开发人员工具,了解所分析的标志名(flagName)和标志值的变体可能会很有用flagVariation在这种情况下,使用小钩子可以帮助我们React.useDebugValue

export default function useFeatureToggle(flagName, flagVariation = true) {
    const flags = React.useContext(FlagsContext)
    const isFeatureEnabled = getIsFeatureEnabled(flagName, flagVariation)(flags)

    React.useDebugValue({
        flagName,
        flagVariation,
        isEnabled: isFeatureEnabled
    })

    return isFeatureEnabled
}

现在,使用React开发人员的工具,我们可以看到有关标志值选项,标志名称以及是否打开了我们感兴趣的功能的信息。


在应用React.useDebugValue钩子之后使用React开发人员工具

请注意,在用户钩子使用标准钩子(例如React.useState或)的情况下React.useRef,此类钩子已经记录了相应的状态或引用对象数据。结果,此处使用视图构造React.useDebugValue({ state })并不是特别有用。

3.挂钩的组合和组成


当我们开始在工作中实现钩子并开始在组件中使用越来越多的钩子时,很快就发现同一组件中可能有5-10个钩子。在这种情况下,使用了各种类型的钩子。例如,我们可以使用2-3个钩子React.useState,然后使用一个钩子React.useContext(例如,以获取有关活动用户的信息),一个钩子React.useEffect,以及其他库中的钩子,例如react-routerreact-intl

结果一次又一次地重复,结果,即使很小的部件也不是那么紧凑。为了避免这种情况,我们开始将这些单独的挂钩提取到用户挂钩中,用户挂钩的设备取决于组件或某些应用程序功能。

想象一下旨在创建订单的应用程序机制。在开发此机制时,使用了许多组件以及各种类型的钩子。这些挂钩可以组合为自定义挂钩,从而提高了组件的可用性。这是在一个挂钩中组合一组小挂钩的示例。

function OrderCreate() {
    const {
        orderCreater,
        orderFetcher,
        applicationContext,
        goToNextStep,
        pendingOrder,
        updatePendingChanges,
        revertPendingChanges
    } = useOrderCreate()

    return (/** ...children */)
}

4. React.useReducer和React.useState的比较


我们经常将其React.useState作为处理组件状态的标准挂钩。但是,随着时间的流逝,组件的状态可能需要变得复杂,这取决于组件的新要求,例如状态中是否存在多个值。在某些情况下,使用React.useReducer可以帮助避免使用多个值的需要并简化更新状态的逻辑。想象一下进行HTTP请求和接收响应时的状态管理。为此,您可能需要使用isLoadingdata等值error取而代之的是,可以使用化简器来控制状态,该化简器可以使用各种动作来管理状态更改。理想情况下,此方法鼓励开发人员以状态机的形式感知接口的状态。

传输React.useReducer的减速器与Redux中使用的减速器类似,在Redux中,系统接收动作的当前状态,并应返回下一个状态。该动作包含有关其类型的信息,以及根据其形成下一个状态的数据。这是一个设计用于控制特定计数器的简单reducer的示例:

const initialState = 0;
const reducer = (state, action) => {
    switch (action) {
        case 'increment': return state + 1;
        case 'decrement': return state - 1;
        case 'reset': return 0;
        default: throw new Error('Unexpected action');
    }
};

该减速器可以单独进行测试,然后使用以下组件在组件中使用React.useReducer

function Component() {
    const [count, dispatch] = React.useReducer(reducer, initialState);

    return (
        <div>
            {count}
            <button onClick={() => dispatch('increment')}>+1</button>
            <button onClick={() => dispatch('decrement')}>-1</button>
            <button onClick={() => dispatch('reset')}>reset</button>
        </div>
    );
};

在这里,我们可以应用前面三部分中讨论的内容,即可以将所有内容提取到中useCounterReducer通过从描述界面元素外观的组件中隐藏动作类型信息,这将改进代码。结果,这将有助于防止实现细节泄漏到组件中,也将为我们提供更多调试代码的机会。生成的自定义钩子和使用它的组件如下所示:

const CounterActionTypes = {
    Increment: 'increment',
    Decrement: 'decrement',
    Reset: 'reset',
}

const useCounterReducer = (initialState) => {
    const [count, dispatch] = React.useReducer(reducer, initialState);

    const increment = React.useCallback(() => dispatch(CounterActionTypes.Increment));
    const decrement = React.useCallback(() => dispatch(CounterActionTypes.Decrement));
    const reset = React.useCallback(() => dispatch(CounterActionTypes.Reset));

    return {
        count,
        increment,
        decrement
    }
}

function Component() {
    const {count, increment} = useCounterReducer(0);

    return (
        <div>
            {count}
            <button onClick={increment}>+1</button>
        </div>
    );
};

5.逐步实现钩子


乍一看,逐渐引入钩子的想法似乎并不完全合乎逻辑,但是在这里我建议那些认为如此的人只是遵循我的推理。随着时间的流逝,各种模式都会在代码库中找到应用。在我们的例子中,这些模式包括高阶组件,渲染属性以及钩子。在将项目转换为新模式时,开发人员不会寻求立即重写所有代码,通常这几乎是不可能的。因此,您需要制定一个计划以将项目转移到React挂钩,其中不包括对代码的重大更改。由于更改代码通常会导致其大小和复杂性增加,因此此任务可能非常困难。通过引入钩子,我们努力避免这种情况。

我们的代码库使用基于类的组件和功能组件。无论在特定情况下使用哪个组件,我们都试图通过React钩子共享逻辑。首先,我们在挂钩中实现逻辑(或重复其中的某些机制的现有实现),然后创建更高级别的小组件,并在其中使用这些挂钩。之后,这些高阶组件将用于创建基于类的组件。结果,我们可以将逻辑放置在一个地方,可以在各种组件中使用。这是通过高阶组件在组件中实现挂钩功能的示例。

export const injectTracking = (propName = 'tracking') => WrappedComponent => {
    const WithTracking = props => {
        const tracking = useTracking();

        const trackingProp = {
            [propName]: tracking,
        };

        return <WrappedComponent {...props} {...trackingProp} />;
    };

    WithTracking.displayName = wrapDisplayName(WrappedComponent, 'withTracking');

    return WithTracking;
};

export default injectTracking;

这显示了挂钩功能useTracking在组件中的实现WrappedComponent顺便说一下,这除其他事项外,使我们得以将实现挂钩和重写测试的任务分离到系统的旧部分中。通过这种方法,我们仍然可以使用在代码库的所有部分中使用钩子的机制。

摘要


以下是一些使用React挂钩如何改进我们的代码库的示例。我们希望钩子也能使您的项目受益。

亲爱的读者们!您是否使用React钩子?


All Articles