[tutorial] Pastate.js Responsive framework (2) multi component application

Time:2021-2-24

This ispastateWelcome to the second chapter of the series of tutorials.

In this chapter, weLast chapterAdd more information to the state structure of, and use multiple components to organize the pastate application.

Update state structure

We package the personal basic information data in the previous chapter asstate.basicInfoProperty, andstateAdd inaddressProperty to save personal address information:

const initState = {
    basicInfo: {
        name: 'Peter',
        isBoy: true,
        age: 10
    },
    address: {
        country: 'China',
        city: 'Guangzhou'
    }
}

Due to the limitation of JavaScript language, pastate can’t detect adding new attributes to objects by assigning values to automatically convert new attributes into responsive nodes. Therefore, you should define all the state attributes that need to be used in initstate. It is OK to initialize the attribute value to null or empty array. Here’s a questionExamples of mistakes

const initState = {
    basicInfo: ...,
    address: ...
}
const store = new Pastore(initState)
const state = store.state

state.hobby  ='coding' // error, state.hobby  The attribute is not controlled by pastate and has no responsive characteristics

Even if this feature is supported, it will make it difficult for developers to fully grasp the structure of state, which makes it difficult for applications to develop and maintain. Therefore, we should make a complete definition of the structure of state in initstate.

The view components of basicinfo and address are developed respectively

First, we use a simple and temporary way to build sub components

...
/** @type {initState} */
const state = store.state;

class BasicInfoView extends Component {
    render(){
        return (
            <div style={{padding: 10, margin: 10}}>
                <strong>Basic info:</strong><br/>
                My name is {state.basicInfo.name}.<br/>
                I am a {state.basicInfo.isBoy == true ? "boy" : "girl"}.<br/>
                I am {state.basicInfo.age} years old.<br/>
            </div>
        )
    }
}
class AddressView extends Component {
    render(){
        return (
            <div style={{padding: 10, margin: 10}}>
                <strong>Address:</strong><br/>
                My country is {state.address.country}.<br/>
                My city is {state.address.city}.<br/>
            </div>
        )
    }
}

As you can see, the basicinfoview component is directly referenced store.state.basicInfo The addressview component directly references the value of store.state.address The value of. Then modify the original appview parent component, nest the two child components, and add a method to modify itaddress.cityValue of:

...
class AppView extends Component {
    increaseAge(){
        state.basicInfo.age += 1
    }
    decreaseAge(){
        state.basicInfo.age -= 1
    }
    changeCity(){
        state.address.city += '!'
    }
    render() {
        return (
            <div style={{padding: 10, margin: 10, display: "inline-block"}}>
                <BasicInfoView />
                <AddressView />
                <button onClick={this.decreaseAge}> decrease age </button> 
                <button onClick={this.increaseAge}> increase age </button> 
                <button onClick={this.changeCity}> change city </button>
            </div>
        )
    }
}
...

Done! Let’s run it

Click the button, it looks like everything is OK! We use Chrome’sreact dev toolsLet’s see how the components render when the state changes. Open the browser’s developer tool, select the react tab, and check highlight updates. When the component is re rendered, it will be framed by colored boxes.

We click on the pagedecrease ageButton, the result of component re rendering is as follows:

We can find that when only state.basicInfo.age When changing, appview, basicinfoview and addressview will be re rendered, even if the data referenced by addressview has not changed! This is the common case of react multi-component rendering. When the application components are simple and the nesting levels are not many, we will not feel that this mode will bring any obvious impact; but when the nesting relationship of application components becomes more complex, it will bring performance risks. We need to pay attention to this problem.

store.imState And store.state

Let’s first introduce two different states in the store:store.imStateandstore.state, you can try to understand:

  • store.imStateIt is a data entity of application state, which is managed by pastate using immutable mechanism. When the content of a node is updated, the “references” of all ancestors of the node will be updated. Each node value of imstate is a wrapper type (string, number, Boolean, object, array) except null or undefined.
  • store.stateYes store.imState OfResponsive shadowYes store.state If any node is directly modified, pastate will apply the modified result to the store.imState And trigger view update asynchronously.

Or it can be simplified as the following two points:

  • store.imStateUsed to render views
  • store.stateUsed to manipulate data

These two concepts are very important for those who have not used Redux and those who have not understood them vue.js The principle may be a little difficult for people to understand. But it doesn’t matter. Not understanding these two concepts doesn’t prevent you from using pastate,You can use pastate without feeling imstate at all. The idea of pastate is to encapsulate complex concepts so that you can implement complex functions in a simple way.

If you want to understand the detailed principle of pastate, you can check it outprincipleChapter.

Using props to receive imstate, the component can be rendered on demand

When a component is connected to the store, the store will pass the imstate to the props of the component
. state, so we can receive the state in props of appview component, and change the base class of appview component to react pure componentPureComponentIn this way, on-demand rendering of components is turned on

Import react, {purecomponent} from 'react'; // 1. Use purecomponent instead of component
...
Class appendview extends purecomponent {// 1. Use purecomponent instead
    ...
    render() {
        /** @type {initState} */
        let state =  this.props.state ; // 2. Receive state from props
        return (
            <div style={{padding: 10, margin: 10, display: "inline-block"}}>

                {/ * * 3. Pass the child node of the state to the child component of the State * /}

                <BasicInfoView state={state.basicInfo}/>
                <AddressView state={state.address}/>
                ...
            </div>
        )
    }
}
...

Note the third comment of the above code. We pass the child nodes of state data to the child components through props
<BasicInfoView state={state.basicInfo}/>. For the sub components that are not directly connected to the store, we also need to modify them to be from the
Props gets the state and changes the base class of the component to purecomponent

Class basicinfoview extends purecomponent {// 1. Change the base class to purecomponent
    render(){
        let state =  this.props.state ; // 2. Receive state from props
        return (
            <div style={{padding: 10, margin: 10}}>
                <strong>Basic info:</strong><br/>

                {/ * * 3. The state here is basicinfo object * /}

                My name is {state.name}.<br/>
                I am a {state.isBoy == true ? "boy" : "girl"}.<br/>
                I am {state.age} years old.<br/>
            </div>
        )
    }
}
Class addressview extends purecomponent {// 1. Change the base class to purecomponent
    render(){
        let state =  this.props.state ; // 2. Receive state from props
        return (
            <div style={{padding: 10, margin: 10}}>
                <strong>Address:</strong><br/>

                {/ * * 3. The state here is the address object * /}

                My country is {state.country}.<br/>
                My city is {state.city}.<br/>
            </div>
        )
    }
}

As you can see, the state in props assigned to the child component is the child node of the root state. So in basicinfoview this.props.state Is a basicinfo object, while in addressview this.props.state Is the address object.

Done! Let’s see how it works!

  • clickdecrease ageButton orincrease ageButton, the component re rendering we see is as follows:

  • clickchange cityButton, the component re rendering we see is as follows:

Amazing! You can see that when we click the button to change the state node,Only components that reference the changed state node are re renderedWe have successfully implemented multi componentRendering on demandGood effect! When the application has a large number of sub components that are not directly connected to the store, this on-demand rendering strategy can greatly improve the rendering performance of the application.

Considerations for rendering views with imstate

Each node of the state received from props is a special wrapper typeif(...)Statement or... ? A : B When using its boolean result, you need to use the==An explicit comparison is made to obtain the following

class BasicInfoView extends PureComponent {

    render() {
        /** @type {initState['basicInfo']} */
        let state = this.props.state;
        return (
            <div style={{ padding: 10, margin: 10 }}>

               { state.isBoy  ==True? "Boy": "girl"} {/ * correct * /}
               { state.isBoy  ? "boy": "girl"} {/ * error * /}

               { state.age  ! = 0? "Not 0": "0"} {/ * correct * /}
               { state.age  ? "not 0": "0"} {/ * error * /}

            </div>
        )
    }
}

Understanding purecomponent

ReactPureComponentShallow comparison is performed between the new props / state and the old props / state before rendering, and re rendering is performed only when the props / state is found to have changed. Shallow comparison is to compare whether the root level attribute value of props / state has changed. If the attribute value is array / object type, the result of comparison will make it invalidquoteEqual or not:

console.log (["a"] = ["a"] // the result is false

let a = ["a"]
console.log (a = = a) // the result is true
console.log ({A: "a"} = = {A: "a"}) // the result is false

let a = {a: "a"} 
console.log (a = = a) // the result is true

The state data of pastate conforming to the immutable data specification can ensure that when a state node changes, the reference of its ancestor node will be updated, so it can be used with purecomponent to achieve efficient on-demand rendering.

When rendering on demand, the structure of state needs to be modularized. If all the attributes are placed on the root node of state, it is impossible to render on demand

//Note: such a state design can not achieve on-demand rendering of subcomponents
initState = {
     name: 'Peter',
     isBoy: true,
     age: 10,
     country: 'China',
     city: 'Guangzhou'
}

Of course, only when the state of the application is complex and there are many operations on the state, can it reflect the performance improvement of rendering on demand; when the application is simple, it is not necessary to divide the state and view in too detail.

IntelliSense of subcomponent state

Similarly, we can use JSDoc annotation to make the state in the subcomponent have intelligent prompts, as follows:

class BasicInfoView extends PureComponent {
    render(){
        /** @type {initState['basicInfo']} */
        let state = this.props.state;
        ...
    }
}
class AddressView extends PureComponent {
    render(){
        /** @type {initState['address']} */
        let state = this.props.state;
        ...
    }
}

Please indicate the child node of the object in the format of XXX [‘xxx ‘]: /** @type {initState['address']} */. In vs code, it is temporarily unavailable xxx.xxx Specifies the type of a variable.

Single instance component

If a component appears only once in a view, it is called a singleton component. This component can simply encapsulate the state operation function designed for the subcomponent in the subcomponent, improve the cohesion of the component, and facilitate the maintenance and management. Next, take basicinfoview as an example to move the operation button into the subcomponent,And move the two operation functions into the subcomponents

...
class BasicInfoView extends PureComponent {

    increaseAge(){
        state.basicInfo.age += 1
    }
    
    decreaseAge(){
        state.basicInfo.age -= 1
    }

    render(){
        /** @type {initState['basicInfo']} */
        let state = this.props.state;
        return (
            <div style={{padding: 10, margin: 10}}>
                ...
                <button onClick={this.decreaseAge}> decrease age </button> 
                <button onClick={this.increaseAge}> increase age </button> 
            </div>
        )
    }
}
...

Similarly, you can do the same with addressview.

In the next chapter, we will show you how to render and manipulate arrays in pastate.