reactjs 使用上下文API的redux区分

chhkpiq4  于 2023-02-22  发布在  React
关注(0)|答案(3)|浏览(162)

我的理解是,React的ContextAPI本质上是为了快速而肮脏的全局状态管理而引入的,特别是在引入Redux Toolkit以简化实现Redux的开销之前。
我的理解是,上下文API的一个主要缺点是,对它的任何属性的任何更新都将重新呈现绑定到上下文API的所有组件字段(完全重新呈现)。
我最近向某人解释了这个缺点,他问为什么Redux不是这样。他说Redux在幕后使用了Context API,这似乎是基于一些谷歌搜索的情况:
https://react-redux.js.org/using-react-redux/正在访问的存储#:~:text =内部%2C%20React%20还原%20使用%20React的,对象%20示例%20由%20React生成。
几个问题:
1-您能否确认Context API在更改任何状态值时会重新呈现整个组件?
你能确认Redux在幕后使用了Context API吗?你能确认Redux工具包是否仍然在幕后使用Context API吗?
3-我的印象是Context API是作为Redux这样的工具的一个更简单的替代品而引入的,但是如果Redux使用Context API,那么Context API是第一位的吗?
4-如果Redux确实在幕后使用了Context API,那么你能提供一些关于Redux如何避免整个组件重新呈现的见解吗?我会假设有某种类型的Map层,但我很有兴趣获得一些关于具体实现细节的更多见解

tkclm6bt

tkclm6bt1#

我的理解是,React的ContextAPI本质上是为了快速而肮脏的全局状态管理而引入的
这是一个常见的误解。上下文不是一个状态管理系统,就像 prop 不是一个状态管理系统一样。上下文(像 prop 一样)是一种从一个组件到另一个组件获取数据的方式。不同之处在于 prop 总是将数据传递给直接子组件,而上下文则将数据提供给子树中感兴趣的任何随机组件。
我的理解是,上下文API的一个主要缺点是,对它的任何属性的任何更新都将重新呈现绑定到上下文API的所有组件字段(完全重新呈现)。
同样,如果你改变了 prop ,接收这些 prop 的组件必须重新呈现
1-您能否确认Context API在更改任何状态值时会重新呈现整个组件?
对于监听该上下文的特定组件,是的。
你能确认Redux在幕后使用了Context API吗?你能确认Redux工具包是否仍然在幕后使用Context API吗?
React-redux确实使用了上下文,但纯reduxredux toolkit不使用上下文,因为它们是通用库,与react没有直接关系,但我想您指的是react-redux。
必须在react-redux应用顶部呈现的<Provider store={store}>是为了向其下的组件提供上下文,调用useSelectoruseDispatch等钩子的组件然后使用上下文来查找它们应该与之交互的商店。
3-我的印象是Context API是作为Redux这样的工具的一个更简单的替代品而引入的,但是如果Redux使用Context API,那么Context API是第一位的吗?
上下文已经存在很长时间了,但它曾经是一个非官方的特性。随着时间的推移,他们也使它更容易使用。
4-如果Redux确实在幕后使用了上下文API,那么你能提供一些关于Redux如何避免整个组件重新渲染的见解吗?
上下文只向子组件提供最少量的内容,最重要的是商店对象。对商店对象的引用通常不会更改,因此由于上下文值不会更改,因此子组件不需要呈现。要查看它提供的内容,请参阅以下代码:www.example.comhttps://github.com/reduxjs/react-redux/blob/master/src/components/Provider.tsx#L33
存储的 * contents * 确实会改变,但上下文并没有提供这些内容。要获取存储的内容,各个组件要订阅存储,使用redux的store.subscribe,外加一个名为useSyncExternalStore的特殊钩子。基本上,redux在存储状态更新时触发一个事件。然后各个组件设置它们自己的局部状态,如果这是它们关心的变化,这个局部状态的变化就是导致重新渲染的原因。
如果你写的是使用上下文的代码,你很少会做一些需要useSyncExternalStore或定制订阅系统的事情,所以你需要记住的主要事情是:
1.保持上下文集中于单个任务。例如,如果您有一个主题对象来控制应用的颜色,还有一个用户对象来描述当前登录的用户,请将它们放在不同的上下文中。这样,当用户更改时,只关心主题的组件就不需要重新呈现,反之亦然。
1.如果你的上下文值是一个对象,记住它,这样它就不会在每次渲染时改变(参见本文档)

4ktjp1zp

4ktjp1zp2#

我是Redux的维护者,@NicholasTower给出了很棒的回答,但要给出更多细节:
Context和Redux是解决不同问题的非常不同的工具,有一些重叠。
Context不是一个"状态管理"工具。它是一种依赖注入机制,其唯一目的是使一个值可以被React组件的嵌套树访问。由您决定该值是什么,以及如何创建它。通常,这是使用来自React组件状态的数据完成的,即useStateuseReducer。因此,您实际上自己在做所有的"状态管理"-上下文只是给了您一种将其传递到树的方法。
Redux是一个库和模式,用于将状态更新逻辑与应用的其余部分分离,并使跟踪状态何时/何地/为何/如何更改变得更加容易。它还使整个应用能够访问任何组件中的任何状态。
此外,Context和(React-)Redux传递更新的方式有一些明显的区别,Context有一些主要的性能限制--特别是,任何使用上下文的组件 * 将 * 被强制重新呈现,即使它只关心上下文值的一部分。
Context本身就是一个很好的工具,我经常在自己的应用程序中使用它。但是,Context并不能"取代Redux"。当然,你可以同时使用它们来传递数据,但它们不是一回事。这就像问"我能用螺丝刀代替锤子吗?"不,它们是不同的工具,你用它们来解决不同的问题。
因为这是一个如此常见的问题,我写了一个 * 广泛 * 的帖子详细说明了差异:

    • 一个

具体回答您的问题:
1.是的,更新一个Context值会强制使用该上下文的 * 所有 * 组件重新呈现......但实际上它们很有可能会重新呈现 * 无论如何 *,因为React默认情况下会递归呈现,并且在父组件中设置状态会导致该父组件中的所有组件重新呈现,除非您特别尝试避免它。这就解释了这一切是如何运作的。
1.是的,React-Redux * 确实 * 在内部使用Context ......但只是传递 * Redux存储示例 *,而不是 * 当前状态值 *。这导致了 * 非常 * 不同的更新特性。另一方面,Redux Toolkit * 只是 * 关于Redux逻辑,而与 * 任何 * UI框架都没有特定的关联。

  1. Context * 没有 * 被引入作为Redux的替代品。在Redux创建之前,React中就有一个"遗留Context" API,React-Redux一直使用到v5。然而,遗留Context API在一些关键方面被破坏了。当前的React Context API是在React 16.3中引入的,以修复遗留Context中的问题,* 而不是 * 专门用于取代Redux。
  2. React-Redux在每个组件示例中使用存储订阅和选择器,这是一种与Context操作方式完全不同的机制。
    我肯定会建议阅读我上面链接的帖子,以及这些其他相关的帖子:
41zrol4v

41zrol4v3#

Mark Erikson的原始博客文章:

我只是复制粘贴一些信息,但这里的原始来源,我建议直接去这里:https://blog.isquaredsoftware.com/2020/01/blogged-answers-react-redux-and-context-behavior/
更多链接:

说明React Context的行为方式以及React-Redux如何在内部使用Context

有几个假设,我已经看到弹出反复:

  • React-Redux“只是React上下文的 Package 器”
  • 如果对上下文值进行反结构化,则可以避免由React上下文引起的重新呈现

这两个假设都是不正确的,我想澄清它们实际上是如何工作的,这样您就可以避免将来误用它们。
对于上下文行为,假设我们有以下初始设置:

function ProviderComponent() {
    const [contextValue, setContextValue] = useState({a: 1, b: 2});

    return (
        <MyContext.Provider value={contextValue}>
            <SomeLargeComponentTree />
        </MyContext.Provider>
    )
}

function ChildComponent() {
    const {a} = useContext(MyContext);
    return <div>{a}</div>
}

如果ProviderComponent随后调用setContextValue({a: 1, b: 3})ChildComponent将重新呈现,即使它只关心基于重构的a字段。 Package useContext(MyContext)调用的钩子有多少层也无关紧要。一个新的引用被传递到提供者,所以所有使用者将重新呈现。实际上,如果我使用<MyContext.Provider value={{a: 1, b: 2}}>显式地重新呈现,ChildComponent仍然会重新呈现,因为一个新的对象引用已经被传递到提供者中!(请注意,这就是为什么你不应该直接将对象常量传递到上下文提供者中,而是保持数据的状态或记住上下文值的创建。
对于React-Redux:是的,它在内部使用上下文,但只是将Redux存储示例向下传递给子组件--它不使用上下文传递存储状态!如果您查看实际的实现,它大致如下,但更加复杂:

function useSelector(selector) {
    const [, forceRender] = useReducer( counter => counter + 1, 0);
    const {store} = useContext(ReactReduxContext);
        
    const selectedValueRef = useRef(selector(store.getState()));

    useLayoutEffect(() => {
        const unsubscribe = store.subscribe(() => {
            const storeState = store.getState();
            const latestSelectedValue = selector(storeState);

            if(latestSelectedValue !== selectedValueRef.current) {
                 selectedValueRef.current = latestSelectedValue;
                 forceRender();
            }
        })
        
        return unsubscribe;
    }, [store])

    return selectedValueRef.current;
}

因此,React-Redux只使用上下文传递存储本身,然后使用store.subscribe()在存储状态发生变化时得到通知,这导致了与使用上下文传递数据非常不同的性能行为。
关于React issue #14110: Provide more ways to bail out of hooks中的上下文行为有一个广泛的讨论,塞巴斯蒂安Markbage在那篇文章中特别提到:
我个人的总结是,新的上下文已经准备好用于低频率的不太可能的更新(如locale/theme)。以旧上下文使用的相同方式使用它也是很好的。即,用于静态值,然后通过订阅传播更新。它还没有准备好用作所有Flux-like状态传播的替代品。
事实上,我们确实尝试过在React-Redux v6的上下文中传递存储状态,结果证明它的性能不足以满足我们的需求which is why we had to rewrite the internal implementation to use direct subscriptions again in React-Redux v7
有关React-Redux实际工作原理的完整细节,请阅读我的帖子The History and Implementation of React-Redux,其中涵盖了内部实现随时间的变化,以及我们实际上如何使用上下文。

相关问题