react 全局状态_React的新上下文API:在本地状态和全局状态之间切换

react 全局状态

by Diego Haz

迭戈·哈兹(Diego Haz)

React的新上下文API:在本地状态和全局状态之间切换 (React's new context API: toggle between local and global state)

Consider a component that handles a visibility state and passes it down to its children via render props:

考虑一个处理可见性状态并将其通过render props传递给其子级的组件:

const PopoverContainer = () => (  <VisibilityContainer>    {({ toggle, hidden }) => (      <div>        <button onClick={toggle}>PopoverButton</button&gt;        <div hidden={hidden}>PopoverContent</div>      </div>    )}  </VisibilityContainer>);

What would you think about being able to make that state global by just changing a context property on the component?

您仅通过更改组件上的context属性就可以使该状态变为全局的想法如何?

const PopoverButton = () => (  <VisibilityContainer context="popover1">    {({ toggle }) => (      <button onClick={toggle}>PopoverButton</button>    )}  </VisibilityContainer>);
const PopoverContent = () => (  <VisibilityContainer context="popover1">    {({ hidden }) => (      &lt;div hidden={hidden}>PopoverContent</div>    )}  </VisibilityContainer>);

That's what we're going to achieve in this article.

这就是我们在本文中要实现的目标。

语境与状态 (Context and State)

First, before talking about context and state in React, let me give you some context on the state of this topic (!).

首先,在讨论React中的上下文状态之前,让我为您提供有关此主题状态上下文 (!)。

Some months ago I published reas, an experimental UI toolkit powered by React and styled-components.

几个月前,我发布了reas ,这是一个由React和styled-components支持的实验性UI工具包。

Besides components themselves, I wanted to provide helpers to handle their state. The approach I took at that time was to export some high-order components (HOCs), such as withPopoverContainer, so as to control the visibility state of a Popover component. Take a look at this example:

除了组件本身,我还想提供一些帮助程序来处理它们的状态。 我当时采用的方法是导出一些高阶组件 ( withPopoverContainer ),例如withPopoverContainer ,以便控制Popover组件的可见性状态。 看一下这个例子:

import { Popover, withPopoverContainer } from "reas";
const MyComponent = ({ toggle, visible }) => (  <div>    <button onClick={toggle}>Toggle</button>    <Popover visible={visible}>Popover</Popover>  </div>);
export default withPopoverContainer(MyComponent);

But HOCs have some problems, such as name collision. What if another HOC or a parent component passes its own toggle prop to MyComponent? Things will certainly break.

但是HOC存在一些问题,例如名称冲突。 如果另一个HOC或父组件将其自身的toggle道具传递给MyComponent怎么办? 事情肯定会破裂。

Even before that, inspired by Michael Jackson and his great talk, the React community started to adopt render props over HOCs.

甚至在此之前,受迈克尔·杰克逊 ( Michael Jackson)及其精彩演讲的启发,React社区开始在HOC上采用渲染道具

Also, React v16.3.0 introduced a new context API, replacing the old unstable one, using render props.

另外,React v16.3.0引入了新的上下文API ,使用渲染道具代替了旧的不稳定 API

I've learned to look at all that stuff that gets hyped up, especially the stuff brought up by the JavaScript community, with a critical eye. This keeps my mind sane and prevents me from having to refactor my code every single day with cool new libraries.

我学会了用批判性的眼光看待所有被炒作的东西,尤其是JavaScript社区提出的东西。 这使我头脑清醒,并使我不必每天都使用很棒的新库来重构代码。

Finally, I posted a tweet asking people which they prefer: render props or HOCs. All comments were favorable to render props, which eventually made me turn all HOCs in reas into components with render props:

最后,我发布了一条推文,询问人们更喜欢哪些:渲染道具或HOC。 所有评论都对渲染道具有利,最终使我将所有HOC变成了带有渲染道具的组件:

import { Popover } from "reas";
const MyComponent = () => (  <Popover.Container>    {({ toggle, visible }) => (      <div>        <button onClick={toggle}>Toggle&lt;/button&gt;        <Popover visible={visible}>Popover</Popover>      </div>    )}  </Popover.Container>);
export default MyComponent;

Popover.Container was a regular React component class with a toggle method using this.setState to change this.state.visible. Simple as that.

Popover.Container是常规的React组件类,带有使用this.setState更改this.state.visibletoggle方法。 就那么简单。

It was good and worked pretty well. However, in one of my projects I had a button that was supposed to control the Popover component placed in a completely different path in the React tree.

很好,并且运作良好。 但是,在我的一个项目中,我有一个button应该用来控制Popover组件,该组件放置在React树中的完全不同的路径中。

I either needed to have some sort of global state manager like Redux, or I needed to move Popover.Container up in the tree in a common parent and pass the props down until they touched both button and Popover . But this sounded like a terrible idea.

我要么需要像Redux这样的全局状态管理器,要么需要将Popover.Container在同一个父级的树中上移, Popover.Container下传递这些道具,直到它们同时碰到buttonPopover 。 但这听起来像一个可怕的主意。

Also, setting up Redux and rewriting all the logic I already had with this.setState into actions and reducers just to have that functionality would be an awful job.

另外,设置Redux并将我已经拥有的this.setState逻辑全部重写为动作和化this.setState ,以具有该功能将是一项this.setState的工作。

I think this imminent need of shared state is one of the reasons why people prematurely optimize their apps. That is, setting up all the libraries they might need up front, which includes a global state management library.

我认为对共享状态的这种迫切需求是人们过早优化其应用程序的原因之一。 也就是说,预先设置他们可能需要的所有库,其中包括全局状态管理库。

React's new context API comes in handy to solve this issue. I wanted to keep using regular React local state and only scale up to global state when needed, without needing to rewrite my state logic. That's why I built constate.

React的新上下文API可以方便地解决此问题。 我想继续使用常规的React局部状态,并且仅在需要时才扩展到全局状态,而无需重写我的状态逻辑。 这就是为什么我建州立

州立 (Constate)

Let's see how PopoverContainer would look with constate:

让我们看看如何PopoverContainer看起来与constate

import React from "react";import { Container } from "constate";
const PopoverContainer = props => (  <Container    initialState={{ visible: false }}    actions={{      toggle: () => state =>; ({ visible: !state.visible })    }}    {...props}  />);
export default PopoverContainer;

Now we can wrap our component with PopoverContainer so as to have access to visible and toggle members already passed by Container to the children function as an argument.

现在,我们可以换我们的组件PopoverContainer以便具有访问visible ,并toggle已经过去的成员Containerchildren作为参数的功能。

Also, note that we are passing all props received from PopoverContainer to Container. This means that we can compose it to create a new derived state component, such as AdvancedPopoverContainer, with new initialState and actions.

另外,请注意,我们PopoverContainer所有从PopoverContainer收到的道具传递给Container 。 这意味着我们可以对其进行组合,以使用新的initialStateactions创建一个新的派生状态组件,例如AdvancedPopoverContainer

引擎盖下 (Under the hood)

If you're like me, and you like to know how things were implemented under the hood, you're probably thinking about how Container was implemented. So, let's recreate a simple Container component:

如果您像我一样,并且想知道事情是如何实现的,那么您可能正在考虑如何实现Container 。 因此,让我们重新创建一个简单的Container组件:

import React from "react";
class Container extends React.Component {  state = this.props.initialState;
render() {    return this.props.children({      ...this.state,      ...mapStateToActions(...)    });  }}
export default Container;

mapStateToActions is a utility function that passes state to each member of actions. That's what makes it possible to define our toggle function like this:

mapStateToActions是一个实用程序函数,它将状态传递给每个actions成员。 这就是可以这样定义我们的toggle功能的原因:

const actions = {  toggle: () =&gt; state => ({ visible: !state.visible})};

Our goal, however, is to be able to use the same PopoverContainer as a global state. With constate we just need to pass a context prop to Container:

但是,我们的目标是能够使用与全局状态相同的PopoverContainer 。 有了constate,我们只需要向Container传递一个context支持:

<PopoverContainer context="popover1">  {({ toggle }) => (    <button onClick={toggle}>PopoverToggle</button>  )}</PopoverContainer>

Now, every Container with context="popover1" will share the same state.

现在,每个具有context="popover1" Container将共享相同的状态。

Of course, you're curious about how Container handles that context prop. So here you go:

当然,您对Container如何处理该context道具感到好奇。 因此,您在这里:

import React from "react";import Consumer from "./Consumer";
class Container extends React.Component {  state = this.props.initialState;
render() {    if (this.props.context) {      return <Consumer {...this.props} />;    }
return this.props.children({      ...this.state,      ...mapStateToActions(...)    });  }}
export default Container;

Ok, I'm sorry. Those four added lines don't tell you much. To create Consumer, we need to understand how to deal with the new React Context API.

好吧,我很抱歉。 这四行添加的内容并没有告诉您太多。 要创建Consumer ,我们需要了解如何处理新的React Context API。

React上下文 (React Context)

We can break the new React Context API into three parts: Context, Provider and Consumer.

我们可以将新的React Context API分为三个部分: ContextProviderConsumer

Let's create the context:

让我们创建上下文:

import React from "react";
const Context = React.createContext();
export default Context;

Then, we create our Provider, which uses Context.Provider and passes state and setState down:

然后,我们创建Provider ,它使用Context.Provider并向下传递statesetState

import React from "react";import Context from "./Context";
class Provider extends React.Component {  handleSetState = fn => {    this.setState(state => ({      state: fn(state.state)    }));  };
state = {    state: this.props.initialState,    setState: this.handleSetState  };
render() {    return (      <Context.Provider value={this.state}>        {this.props.children}      </Context.Provider>    );  }}
export default Provider;

It can be a little tricky. We can't simply pass { state, setState } as a literal object to Context.Provider's value since it would recreate that object on every render. Learn more here.

这可能有点棘手。 我们不能简单地将{ state, setState }作为文字对象传递给Context.Providervalue因为它会在每个渲染器上重新创建该对象。 在这里了解更多。

Finally, our Consumer needs to use Context.Consumer to access state and setState passed by Provider:

最后,我们的Consumer需要使用Context.Consumer来访问Provider传递的statesetState

import React from "react";import Context from "./Context";
const Consumer = ({ context, children, actions }) => (  <Context.Consumer>    {({ state, setState }) =&gt; children({      ...state[context],      ...mapContextToActions(...)    })}  </Context.Consumer>);
export default Consumer;

mapContextToActions is similar to mapStateToActions. The difference is that the former maps state[context] instead of just state.

mapContextToActionsmapStateToActions相似。 不同之处在于,前者映射state[context]而不是state

The final step is to wrap our app with Provider:

最后一步是使用Provider包装我们的应用Provider

import React from "react";import ReactDOM from "react-dom";import Provider from "./Provider";
const App = () => (  <Provider>    ...  &lt;/Provider>);
ReactDOM.render(<App />, document.getElementById("root"));

Finally, we have rewritten constate. Now you can use Container component to switch between local and global state with ease.

最后,我们重写了constate 。 现在,您可以使用Container组件轻松地在本地状态和全局状态之间切换。

结论 (Conclusion)

You might be thinking that starting a project with something like constate could also be a premature optimization. And you're probably right. You should stick with this.setState without abstractions as long as you can.

您可能会认为,使用constate之类的项目启动项目可能还为时过早。 你可能是对的。 您应该尽可能地坚持this.setState而不进行抽象。

However, not all premature optimizations are the root of all evil. You should find a good balance between simplicity and scalability. That is, you should pursue simple implementations, specially if you're building small applications. But, if you're planning to grow, you should look for simple implementations that are also easy to scale.

但是,并非所有过早的优化都是万恶之源 。 您应该在简单性和可伸缩性之间找到良好的平衡。 也就是说,您应该追求简单的实现,尤其是在构建小型应用程序时。 但是,如果您打算增长,则应该寻找易于扩展的简单实现。

谢谢您阅读此篇! (Thank you for reading this!)

If you like it and find it useful, here are some things you can do to show your support:

如果您喜欢它并觉得有用,则可以执行以下操作以显示您的支持:

翻译自: https://www.freecodecamp.org/news/reacts-new-context-api-how-to-toggle-between-local-and-global-state-c6ace81443d0/

react 全局状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值