桥接模式容器模式主机模式
The “Container” pattern is a concept introduced in the Unstated-Next library. The pattern thinks about state as a variety of “Containers” that hold a modular slice of the global application state. To provide this state you create a Context across your application, then you can access it through hooks.
“容器”模式是Unstated-Next库中引入的概念。 该模式将状态视为包含全局应用程序状态的模块切片的各种“容器”。 要提供此状态,您可以在整个应用程序中创建一个Context,然后可以通过钩子对其进行访问。
Compared to something like Redux, this Container pattern offers a hook-centric way to manage state. It’s easier to learn, scales well with your application, and provides an intuitive way to think about global state. Here’s how it works.
与Redux之类的东西相比,此Container模式提供了一种以挂钩为中心的方式来管理状态。 它易于学习,可与您的应用程序很好地扩展,并提供了一种思考全局状态的直观方法。 运作方式如下。
The container pattern is a methodology where instead of having all your global state in one external library or “global store” such as Redux, you divide that state into multiple chunks called “Containers”. These chunks are responsible for managing their own state and can be pulled into any functional component in the app by using something similar to the following syntax:
容器模式是一种方法,您无需将所有全局状态存储在一个外部库或Redux这样的“全局存储”中,而是将状态划分为多个称为“容器”的块。 这些块负责管理自己的状态,可以通过使用类似于以下语法的方式将它们放入应用程序的任何功能组件中:
const {user} = Auth.useContainer();This pattern works really well because it divides state up into self-managing chunks rather than having everything intertwined. Each component can simple pull in the chunk of state that it wants to use and is only dependent on a part of your applications state.
这种模式的确非常有效,因为它将状态划分为自我管理的块,而不是将所有事物交织在一起。 每个组件都可以简单地提取其要使用的状态块,并且仅取决于应用程序状态的一部分。
Each chunk of state is really easy to reason about. It’s simply a custom hook wired up to a context provider. That’s it. The term “Containers” is really just a wrapper term to mean “a React Custom Hook + a Context Provider”, so when someone is recommending state management with hooks + useContext, they’re technically recommending this container pattern.
每个状态块真的很容易推断。 它只是连接到上下文提供者的自定义钩子。 而已。 术语“容器”实际上只是一个包装术语,意为“ React Custom Hook + Context Provider”,因此当有人建议使用hooks + useContext进行状态管理时,他们在技术上建议使用此容器模式。
To use containers you just have to import the Context and use the hook. You don’t technically need any external libraries, however I use a library called Unstated-Next because it gives me some benefits that make this pattern even easier.
要使用容器,只需导入上下文并使用挂钩。 从技术上来说,您不需要任何外部库,但是我使用了一个称为Unstated-Next的库,因为它给了我一些好处,使该模式更加容易。
Unstated-Next is a tiny library that helps us reason about these global containers a little bit easier. This library is tiny (like 200 bytes tiny), and it’s for good reason because it basically doesn’t do anything on top of what React’s Context API already does.
Unstated-Next是一个微型库,可以帮助我们更轻松地推断这些全局容器。 这个库很小 (例如200个字节),这是有充分的理由的,因为它除了React的Context API已经做的之外,基本上不做任何事情。
This library is 100% optional for this design pattern. It just provides small API improvements that make Context easier to work with. Some of the main benefits include:
对于该设计模式,该库是100%可选的。 它只是提供了一些小的API改进,使Context更易于使用。 一些主要好处包括:
Type-Checking: It gives you typescript support out of the box. This was one of my gripes with using the React Context API, so it’s nice to see that unstated-next solves that issue.
类型检查:提供开箱即用的打字稿支持。 这是我使用React Context API的烦恼之一,因此很高兴看到unstated-next解决了该问题。
Error Handling: If you try to access a container that doesn’t have a Context provider above it in the React DOM tree, it will throw an error. This is a life-saver for debugging.
错误处理:如果您尝试访问一个在React DOM树中上方没有Context提供程序的容器,它将抛出错误。 这是调试的救命稻草。
Easier to Think About: Thinking about contexts can seem abstract at times, but using this library with the mental concept of “containers” is a lot easier to reason about.
更容易思考:对上下文的思考有时看起来很抽象,但是使用带有“容器”思想概念的库就容易多了。
When I use this pattern, I put all my containers in a “container” folder at the root of the src directory.
使用这种模式时,我将所有容器放在src目录根目录下的“容器”文件夹中。
I suffix each container with the word “Container” and have all the relevant code for a container all colocated in one file.
我在每个容器后缀“ Container”,并将容器的所有相关代码都共存于一个文件中。
This already has benefits over something like Redux, where a single responsibility might be divided over 3 or 4 files for the actions, reducer, store, selectors etc.
与Redux之类的东西相比,这已经具有了好处,在Redux中,一个动作可能被分为3个或4个文件用于操作,reduce,store,selector等。
The container is where your slice of state will live. This file contains everything necessary for reading and writing to this part of state. Here’s what a container file may look like for an AuthContainer:
容器是您的状态片所在的位置。 该文件包含读取和写入此状态部分所需的所有内容。 这是AuthContainer的容器文件的外观:
// The reducer. This would be very similar to your reducer in Redux.// This is optional, you can just use useState instead, but this is// here to show that if you want to use a reducer and do more// complicated state transitions you can.function authReducer(state: AuthState, action: Action) { ...}// Custom Hookfunction useAuth(initialState: AuthState) { const [state, dispatch] = useReducer(authReducer, initialState);const loginWithGoogle = () => { dispatch(loggingIn()); doGoogleLogin() .then(user => dispatch(success(user))) .catch(err => dispatch(error(err.message))); }const loginWithEmailPassword = (email, password) => { dispatch(loggingIn()); doEmailPasswordLogin(email, password) .then(user => dispatch(success(user))) .catch(err => dispatch(error(err.message))); }const logout = () => dispatch(logout());return { user: state.data, isAuthenticating: state.loading, error: state.error, loginWithGoogle, loginWithEmailPassword, logout };}// Create the Container (this can be a Context too)// You just pass in the custom hook that you want to build the// container for.export const Auth = createContainer(useAuth);This is really clean because it’s basically just a custom hook and then that little line at the bottom to make it a container. When you add that container code at the bottom, it makes this custom hook have the same state even if used in multiple different components. This is because the Unstated-Next containers just use the Context API under the hood.
这真的很干净,因为它基本上只是一个自定义钩子,然后在底部的那条小线使其成为容器。 当您在底部添加该容器代码时,即使在多个不同的组件中使用,它也使此自定义钩子具有相同的状态。 这是因为Unstated-Next容器仅在后台使用Context API。
To make that work you first need to add a Store to your application which will store all the containers. This might look something like this:
为此,您首先需要在应用程序中添加一个存储所有存储容器的存储。 可能看起来像这样:
Side-Note: I think there could be a better way to manage a Store like this. If there were a way to dynamically create this structure based on an array of containers or something like that, I think that would be a lot cleaner.
旁注:我认为可能会有更好的方法来管理这样的商店。 如果有一种方法可以基于一系列容器或类似的东西动态创建此结构,那我认为这会更清洁。
Also if there was a way to make all these load at the same level of the DOM so any container can access any other container that would be amazing too, but sadly I think that’s a limitation with React.
另外,如果有一种方法可以在DOM的相同级别上进行所有这些加载,那么任何容器都可以访问任何其他容器,这也将是惊人的,但是可悲的是,我认为这是React的局限性。
You’ll want to slot this in the root component so your root component looks something like this:
您需要将其插入根组件,以便您的根组件看起来像这样:
const App: React.FC = () => { return ( <Store> <ReactRouter> <AppRoutes> </ReactRouter> </Store> );}And voila! If you did this correctly, you should now be able to go into any of your React components and use this hook like the following:
瞧! 如果您正确地做到了这一点,那么您现在应该可以进入任何React组件,并使用如下所示的钩子:
const LoginPage: React.FC = () => { ... const { formLogin, googleLogin, isAuthenticating, user } = Auth.useContainer(); useEffect(() => { if (user) { history.push('/home'); } }, [user]); ... return ( <div> <button onClick={() => googleLogin()}> Login with Google </button> ... </div> );}If you did everything correct, following this pattern should work for you! If you did something wrong Unstated-Next might throw an error that says that the container’s provider hasn’t been created, but that’s good because it’s an explicit error message for a bug that can be really difficult to track down if you’re using the basic React Context.
如果您所做的一切正确,那么遵循此模式将为您工作! 如果您做错了什么,Unstated-Next可能会抛出一个错误,提示尚未创建容器的提供程序,但这很好,因为这是一条明确的错误消息,指出如果您使用基本的React上下文
Redux is great for state management at a large scale. It’s the tried-and-tested way to manage state for large applications. However, for the vast majority of applications out there, Redux is the wrong place to start. It’s very boilerplate heavy and likely isn’t going to give you many benefits unless you already know your use-case demands it.
Redux非常适合大规模的状态管理。 这是管理大型应用程序状态的久经考验的方式。 但是,对于绝大多数应用程序来说,Redux是错误的起点。 它非常繁琐,除非您已经知道用例需要它,否则可能不会给您带来很多好处。
Therefore I’m offering this pattern as an alternative.
因此,我提供了这种模式作为替代。
The main benefit you get from this pattern is that it makes more sense from a developer’s perspective. Redux takes all your state and pulls it away from the view layer. I’d argue that a better way to manage state would be to colocate it with the view layer that uses it.
从这种模式中获得的主要好处是,从开发人员的角度来看,它更有意义。 Redux将获取您的所有状态,并将其从视图层拉开。 我认为更好的管理状态的方法是将其与使用状态的视图层并置。
This is why React Hooks exist.
这就是为什么存在React Hooks的原因。
You can already see things moving towards this methodology with the movement of other pieces of state out of things like Redux and into hooks:
您已经看到其他状态从Redux之类进入钩子的过程正在朝着这种方法发展:
Local state => useState / useReducer
本地状态 => useState / useReducer
API state => React-Query / useSWR / Apollo
API状态 => React-Query / useSWR / Apollo
Form state => React Hook Form
表单状态 => React Hook表单
Therefore, it makes sense that the global state also be built to fit well into a hook ecosystem.
因此,有意义的是,还应将全球状态构建为非常适合钩子生态系统。
Since a majority of my state management is done by various hook libraries, it makes sense that my global state management also be hook-centric.
由于我的大部分状态管理是由各种挂钩库完成的,因此我的全局状态管理也应以挂钩为中心。
The container pattern implements this idea. It offers the majority of the functionality as Redux at a fraction of the time-cost and is designed with hook-centric development at the forefront.
容器模式实现了这个想法。 它以Redux的形式提供了大部分功能,而时间成本却很少,并且在设计时就以钩子为中心进行了开发。
For any small-medium sized-project, this pattern is a no-brainer for me. For a larger project, it depends on the use-case.
对于任何中小型项目,这种模式对我来说都是轻而易举的。 对于较大的项目,这取决于用例。
Here’s some comparisons between the container pattern and Redux:
这是容器模式和Redux之间的一些比较:
With this in mind, hopefully you now have a better idea of what sort of alternatives exist if your use-case doesn’t demand something as bulky as Redux.
考虑到这一点,希望您现在能更好地了解如果用例不要求像Redux那样大的东西存在的替代方案。
If you want to employ this pattern but can’t quite leave Redux, another alternative would be a using Redux Toolkit + Redux Ducks Pattern.
如果您想使用此模式但不能完全离开Redux,另一种选择是使用Redux Toolkit + Redux Ducks Pattern 。
Redux Ducks Redux鸭This Redux Ducks approach works well if you’re building a large application because it uses this container-focused methodology, but still keeps you in the ecosystem of Redux.
如果您正在构建大型应用程序,则此Redux Ducks方法会很好用,因为它使用了这种以容器为中心的方法,但仍使您处于Redux生态系统中。
This is the Container pattern. If you’re looking at using Redux in an app, I would take a serious look at the cost of doing so to determine if your application actually requires it. I think this pattern is a good place to start regardless, and because it’s so small and modular you can migrate it into Redux in the future really easily.
这是容器模式。 如果您要在应用程序中使用Redux,我会认真考虑这样做的成本,以确定您的应用程序是否真正需要它。 我认为无论如何,这种模式都是一个不错的起点,并且由于它很小且模块化,因此您将来可以非常轻松地将其迁移到Redux。
Overall, this pattern has helped me clean up my codebase a lot and remove state management from my list of pain-points when developing applications.
总的来说,这种模式帮助我清理了很多代码库,并在开发应用程序时从痛点列表中删除了状态管理。
Anyways, Let me know what you think and hopefully it will work well in your projects. Enjoy!
无论如何,请让我知道您的想法,并希望它能在您的项目中很好地工作。 请享用!
Check me out: https://spencerpauly.com
检查我: https : //spencerpauly.com
翻译自: https://medium.com/@spencerpauly/the-container-pattern-for-better-state-management-in-react-9351fe4381d1
桥接模式容器模式主机模式