Home >Development Tools >composer >Detailed steps for encapsulating React Context Composer (share)

Detailed steps for encapsulating React Context Composer (share)

藏色散人
藏色散人forward
2021-12-20 13:57:252191browse

This article is written by composer Tutorial column to introduce to you how to encapsulate a React Context Composer step by step. I hope it will be helpful to friends in need!

How do I encapsulate a React Context Composer step by step?

Motivation

There are many state management solutions for React, such as Redux, Mobx, Recoil, etc. So far, I have only experienced Redux, and I think it is still a bit cumbersome. . Because I usually write a lot of Hooks, I prefer to use Context Provider with the useContext hook, which makes it easy to split and combine states. Here, we will not discuss the pros and cons of each state management solution, but focus on a multi-layer nesting problem encountered when using Context.

The picture below is some code extracted from a taro react hooks ts project I was writing recently. I split some global state (the purpose of splitting is to reduce unnecessary re-rendering) and then nested them. This way of writing reminds me of the feeling of being dominated by callback hell, which is very uncomfortable. Therefore, I thought of sealing a high-order component myself and "flattening" the structure in terms of writing.

<LoadingContext.Provider value={{ loading, setLoading }}>
  <UserDataContext.Provider value={{ name: "ascodelife", age: 25 }}>
    <ThemeContext.Provider value={"light"}>
    {/* ....more Providers as long as you want */}
    </ThemeContext.Provider>
  </UserDataContext.Provider>
</LoadingContext.Provider>

The easiest solution

Here, I quickly wrote the first solution, using reduceRight to complete the nesting of Provider.

The reason reduceRight is used here instead of reduce is that we are more accustomed to the writing order from the outer layer to the inner layer.

// ContextComposer.tsx
import React from &#39;react&#39;;
type IContextComposerProps = {
  contexts: { context: React.Context<any>; value: any }[];
};
const ContextComposer: React.FC<IContextComposerProps> = ({ contexts, children }) => {
  return (
    <>
      {contexts.reduceRight((child, parent) => {
        const { context, value } = parent;
        return <context.Provider value={value}>{child}</context.Provider>;
      }, children)}
    </>
  );
};
export default ContextComposer;
// App.tsx
<ContextComposer
  contexts={[
    { context: ThemeContext, value: "light" },
    { context: UserDataContext, value: { name: "ascodelife", age: 25 } },
    { context: LoadingContext, value: { loading, setLoading } },
  ]}>
    { children }
</ContextComposer>

After actual experience, I found that although it can be used, the development experience is a little bit worse. The problem is that the value passed when the component enters the parameter is of type any, which means that the static type check of ts is abandoned. When passing parameters, since static type checking will not be done on the value, not only will there not be any code prompts when typing the code, but it may also cause some relatively low-level runtime errors. Bad review!

Transformation plan based on React.cloneElement()

In order to transform the above scheme, I turned to a relatively unpopular but easy-to-use function——React. cloneElement(). There are not many points worth noting about this function. Mainly take a look at its three input parameters. The first is the parent element, the second is the parent props, and the third is the remaining parameters...children, except for the first parameter. Except for this, all other values ​​are optional.

For example:

<!-- 调用函数 -->
React.cloneElement(<div/>,{},<span/>);
<!-- 相当于创建了这样一个结构 -->
<div> 
    <span></span>
</div>

Then let’s start the transformation. The reduceRight frame remains unchanged. Change the input parameter type and reduceRight callback.

// ContextComposer.tsx
import React from &#39;react&#39;;
type IContextComposerProps = {
  contexts: React.ReactElement[];
};
const ContextComposer: React.FC<IContextComposerProps> = ({ contexts, children }) => {
  return (
    <>
      {contexts.reduceRight((child, parent) => {
        return React.cloneElement(parent,{},child);
      }, children)}
    </>
  );
};
export default ContextComposer;
// App.tsx
<ContextComposer
  contexts={[
      <ThemeContext.Provider value={"light"} />,
      <UserDataContext.Provider value={{ name: "ascodelife", age: 25 }} />,
      <LoadingContext.Provider value={{ loading, setLoading }} />,
  ]}>
    { children }
</ContextComposer>

After the transformation, when we pass parameters, it seems that we are really creating a component (of course, the component is actually created, but the component itself is not rendered to the virtual Dom, and is actually rendered. is a cloned copy). At the same time, the static type checking problem of value that we just focused on has also been solved.

tips: React.cloneElement(parent,{},child) is equivalent to React.cloneElement(parent,{children:child}), do you know why?

Related resources

The source code has been synchronized to github (https://github.com/ascodelife/react-context-provider-composer).

It has also been packaged into the npm warehouse (https://www.npmjs.com/package/@ascodelife/react-context-provider-composer). Welcome to experience it.

The above is the detailed content of Detailed steps for encapsulating React Context Composer (share). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete