"React advanced" change your posture and watch hooks! New ideas of inspiration combination and logical view separation in HOC mode

I. Preface

Students who understand the essence of JSX know that it is just a syntax sugar, which will be processed by babel into the form of createElement, and finally become a regular js object. Therefore, we can process the element object in the js logic layer, and it becomes natural to take custom hooks as the element logic processing layer.

In this article, we will study some other uses of custom hooks, how to deal with view layers, and some new ways to play.

4.jpeg

Second, handle element objects with hooks

1 scenario I

It is common for hooks to deal with element. For example, we cache the contents of some UI layers, such as the following scenario.

function Test(){
    console.log('test rerender')
    return <div>
        hello, react !
    </div>
}

function Index({ value }){
    const [ number , setNumber ] = React.useState(0)
    const element = React.useMemo(()=> <Test /> ,[ value ])
    return <div>
        {element}
        <button onClick={() => setNumber(number +1 )} > click {number} </button>
    </div>
}
copy

As above, useMemo cache is used to process the element object corresponding to the Test component. Then, when the value in the Index changes, useMemo will be executed again to get a new element object.

When you click the button, it will trigger setNumber to change the state and trigger the update of Index. However, useMemo will directly read the cached value, so the performance experience is that Test will not be updated again.

This is an optimization strategy based on hooks, which is essentially the caching of element s. After this scheme is processed, the Index no longer needs a memo component package similar to HOC. You can customize the optimization in the rendering direction according to the conditions and directions. This is a parent - > child optimization scheme.

There are some more complex scenes, that is, multiple hooks are combined to achieve the goal.

function Index(){
    const [ number , setNumber ] = React.useState(0)
    const { value } = React.useContext(valueContext)
    const element = React.useMemo(()=> <Test /> ,[ value ])
    return <div>
        {element}
        <button onClick={() => setNumber(number +1 )} > click {number} </button>
    </div>
}
copy

Read the value attribute in the valueContext through useContext, and the Test component subscribes to the change of value. When the value in the context changes, the element object is regenerated, that is, the Test component is re rendered.

Scene 2

After react router v6 comes out, there is a brand-new hooks - useRoutes. It can accept the js routing tree of routing configuration and return an element tree of view layer. Let's take a look at the specific use.

const routeConfig = [
  {
     path:'/home',
     element:<Home />
  },
  {
     path:'/list/:id',
     element:<List />
  },
  {
     path:'/children',
     element:<Layout />,
     children:[
       { path:'/children/child1' , element: <Child1/> },
       { path:'/children/child2' , element: <Child2/>  }
     ]
  }
]

const Index = () => {
  const element = useRoutes(routeConfig)
  return <div className="page" >
    <div className="content" >
        <Menus />
        {element}
    </div>
  </div>
}
const App = ()=> <BrowserRouter><Index /></BrowserRouter>

copy

useRoutes is user-defined hooks and returns a standardized routing structure. Hooks is no longer only responsible for logical processing as we usually do. In this scenario, hooks completely acts as a view container.

2.jpeg

In this mode, the understanding of custom hooks breaks the traditional concept. Maybe this transformation from logic layer to view layer will make some students uncomfortable, but these are not important. We need to have a change in thinking, which is important.

Three design patterns

Let's imagine a scenario. Can custom hooks implement a design scenario, which is similar to the combination of composite mode and hoc mode, and can realize the separation of logic and view?

1. Disadvantages of traditional combination mode

First, let's take a look at the combination mode. The traditional combination mode is as follows:

function Index(){
    return <GrandFather>
            <Father>
                <Son>{null}</Son>
            </Father>
        </GrandFather>
}
copy

In the above, the combination mode is carried out through three components: GrandFather, Father and Son. In this mode, if the combined internal and external components need to establish association and communication, some communication methods need to be mixed through cloneElement.

Taking the above as an example, if we want to realize father < - > son two-way communication, we need to deal with it as follows:

/* Parent component */
function Father({ children }){
    const [ fatherSay , setFatherSay ] = React.useState('')
    const toFather= ()=> console.log('son to father')
    const element = React.cloneElement(children,{ fatherSay ,toFather })
   return <div>
        <p> Father </p>
        <button onClick={() => setFatherSay('father to son')} >to Son</button>
       {element}
   </div>
}

/* Subcomponents */
function Son({ children, fatherSay, toFather }){
    console.log(fatherSay)
   return <div>
       <p> son </p>
       <button onClick={toFather} >to Father</button>
       {children || null}
   </div>
}
copy

Above

  • The Father component mixes the toFather method into props through cloneElement. Son components can directly get this method through props and communicate with the parent component to realize son - > Father.
  • Father can change fatherSay through useState and pass it to Son to realize father - > Son.

One obvious drawback is:

toFather, cloneElement and other logic need to be handled separately by developers, that is, the logic layer and ui layer are strongly related. This requires developers to process logic in the upper and lower components of the composite mode respectively.

If you add the GrandFather component, you need to deal with it like the following figure:

3.jpeg

2. Provide idea in hoc nesting

Hoc itself is a function that receives the original component and returns a new component. Multiple hoc can be nested.

function Index(){
    /* .... */
}
export default HOC1(styles)(HOC2( HOC3(Index) )) 
copy

HOC1 -> HOC2 -> HOC3 -> Index

hoc1.jpg

So can we use the idea of hoc to realize the combination mode and solve the logical redundancy.

3. Implement with custom hooks

Combined with what I mentioned at the beginning, ui logic can be handled through custom hooks, so the above defects of composite mode can be solved through multi-layer nested hooks similar to hoc.

The design of customized hooks is as follows:

useComposeHooks( component, Layout , mergeProps )
copy
  • Component is the component that needs to be processed through the combination mode.
  • Container components that need to be combined.
  • mergeProps new props that need to be merged.
  • useComposeHooks can be used in multiple nests. For example:
function Index(){
    const element = useComposeHooks( useComposeHooks( useComposeHooks(...) , Layout2,mergeProps ) ,Layout1,mergeProps)
    return element
}
copy

Equivalent to:

<Layout1>
   <Layout2>
     { ... }
   </Layout2>
</Layout1>
copy

Next, let's implement this function.

IV. code implementation and effect verification

1 write useComposeHooks

Next, we write useComposeHooks:

function useComposeHooks(component, layout, mergeProps) {
    const sonToFather = useRef({})
    const fatherToSon = useRef({})
    /* Child to parent component communication  */
    const sonSay = React.useCallback((type, payload) => {
        const cb = sonToFather.current[type]
        typeof cb === 'function' && cb(payload)
    }, [component])
    /* Parent and child components */
    const listenSonSay = React.useCallback((type, fn) => {
        sonToFather.current[type] = fn
    }, [layout])
    /* Parent to child component communication*/
    const fatherSay = React.useCallback((type,payload)=>{
        const cb = fatherToSon.current[type]
        typeof cb === 'function' && cb(payload)
    },[layout])
    /* Child listening parent component */
    const listenFather = React.useCallback((type,fn)=>{
        fatherToSon.current[type] = fn
    },[ component ])
    const renderChildren = React.useMemo(() => {
        return component ? React.cloneElement(component, { listenFather, sonSay }) : null
    }, [component])
    return layout ? React.createElement(layout, { fatherSay,listenSonSay, ...mergeProps, children: renderChildren }) : renderChildren
}

copy
  • Save the communication method through useRef.
  • Write sonSay (child to parent component communication), listenSonSay (parent listens to child component), fatherSay (parent to child component communication), listenFather (child listens to parent component) methods.
  • Clone inner components through cloneElement.
  • Create an outer component through createElement.

2 test demo

function GrandFather({ name, children }) {
    return <div>
        <p> {name} </p>
        {children}
    </div>
}

function Father({ children, listenSonSay, name ,fatherSay}) {
    listenSonSay('sonSay', (message) => console.log(message))
    return <div>
        <p> {name} </p>
        <button onClick={() => fatherSay('fatherSay','hello,son!')} >to Son</button>
        {children}
    </div>
}

function Son({ children, sonSay,listenFather ,name }) {
    listenFather('fatherSay',(message) => console.log(message) )
    return <div>
        <p> {name} </p>
        <button onClick={() => sonSay('sonSay', 'hello,father!')} >to Father</button>
        {children || null}
    </div>
}
export default function Index() {
    return (
        useComposeHooks(
            useComposeHooks(
                useComposeHooks(null, 
                    Son, { name: 'Son' })
                , Father, { name: 'Father' })
        , GrandFather, { name: 'GrandFather' })
    )
}
copy
  • As mentioned above, we no longer need to do other processing to the business layer. Just call the relevant methods in props.

Next, let's look at the effect (non moving picture):

5.jpeg

As above, it is perfectly realized. Through this case, I mainly show you that custom hooks implements the combination mode. Don't pay too much attention to the details of the code.

V. summary

Today, through a creative idea, I talked about some other ways to play custom hooks. Of course, the demo in this article is only a case and cannot be used in real business scenarios. Through this article, I hope you have a new understanding of hooks. Finally, thank you for reading

Posted by coldfiretech on Tue, 17 May 2022 08:14:36 +0300