[tutorial] Pastate.js Responsive frame (3) array rendering and operation

Time:2021-2-23

This isPastate.jsChapter 3 of the series of tutorials on responsive react state management framework. Welcome to pay attention and keep updating.

In this chapter, let’s look at how to render and process arrays in state in pastate.

Render array

First, let’s update the structure of state

const initState = {
    basicInfo: ...,
    address: ...,
    pets: [{
        id:'id01',
        name: 'Kitty',
        age: 2
    }]
}

We define an array of object elements initState.pets And the array has an initial element.

Next, we define related components to display the values of pets

class PetsView extends PureComponent {
    render() {
        /** @type {initState['pets']} */
        let state = this.props.state;
        return (
            <div style={{ padding: 10, margin: 10 }}>
                <div><strong>My pets:</strong></div>
                {state.map(pet => <PetView state={pet} key={pet.id}/>)}
            </div>
        )
    }
}
class PetView extends PureComponent {
    render() {
        /** @type {initState['pets'][0]} */
        let state = this.props.state;
        return (
            <div>
                <li> {state.name}: {state.age} years old.</li>
            </div>
        )
    }
}

Two components are defined here. The first is petsview, which is used to display pets array; the second is petview, which is used to display pet elements.
Next, put the petsview component into the appview component to display:

...
class AppView extends PureComponent {
    render() {
        /** @type {initState} */
        let state = this.props.state;
        return (
            <div style={{ padding: 10, margin: 10, display: "inline-block" }}>
                <BasicInfoView state={state.basicInfo} />
                <AddressView state={state.address} />
                <PetsView state={state.pets} />
            </div>
        )
    }
}
...

Done! We have successfully rendered an array object, which is the same as rendering an array with native react. The result of the page is as follows:

Modify array

First, we want to add or reduce array elements, which is very simple to implement with Pasate. By vue.js Inspired by pastate store.state The following 7Array mutation methodYou can call these array functions directly, and pastate will automatically trigger the view update. The seven arrays are mutated as follows

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

Let’s try to update the array with push and pop

class PetsView extends PureComponent {

    pushPet(){
        state.pets.push({
            id: Date.now() + '',
            name: 'Puppy',
            age: 1
        })
    }

    popPet(){
        state.pets.pop()
    }

    render() {
        /** @type {initState['pets']} */
        let state = this.props.state;
        return (
            <div style={{ padding: 10, margin: 10 }}>
                <div><strong>My pets:</strong></div>
                {state.map(pet => <PetView state={pet} key={pet.id}/>)}
                <div>
                    <button onClick={this.pushPet}>push pet</button>
                    <button onClick={this.popPet}>pop pet</button>
                </div>
            </div>
        )
    }
}

Very easy. We have also added two buttons and specified the click processing function to run and experience:

Open the highlight updates option of react dev tools and click the push or pop button to observe the view update. As we wish:

Empty initial array and editor IntelliSense

Usually, the initial value of an array node is empty. In order to implement the editor IntelliSense, we can define aElement type, and note that the element of this array node is of this type:

const initState = {
    ...
    /** @type {[pet]} */
    pets: []
}
const pet = {
    id: 'id01',
    name: 'Kitty',
    age: 2
}

You can also use the generic format to define array types/** @type {Array<pet>} */

Internal action processing of multi instance component

We mentioned that in the last chapterSingle instance component, which means that the component is only used once; we can see that petview is used to display array elements and will be used many times. We put this kind ofIt is used in many placesThe components of are calledMulti instance component. The processing logic of the internal actions of a multi instance component depends on the specific location of the component instance, which is different from the processing mode of a single instance component. Let’s take a look.

We try to make a pet view and add two buttons to adjust the pet’s age

Traditional scheme of react

Traditional solution 1: parent component processing

The parent component passes the binding index handler to the child componentIn this mode, the action processing logic of the child component is implemented in the parent component, and then the parent component binds the action to the corresponding index and passes it to the child component

class PetsView extends PureComponent {
    ...
    addAge(index){
        state.pets[index].age += 1
    }
    reduceAge(index){
        state.pets[index].age -= 1
    }
    render() {
        /** @type {initState['pets']} */
        let state = this.props.state;
        return (
            <div style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={pet.id} 
                            addAge={() =>  this.addAge (index)} // bind the index value and pass it to the child component
                            reduceAge={() =>  this.reduceAge (index)} // bind the index value and pass it to the child component
                        />)
                }
                ...
            </div>
        )
    }
}
class PetView extends PureComponent {
    render() {
        /** @type {initState['pets'][0]} */
        let state = this.props.state;
        return (
            <div >
                <li> {state.name}: 
                    <button onClick={ this.props.reduceAge }>- < / button > {/ * use the bound action handler * /}
                    {state.age} 
                    <button onClick={ this.props.addAge }>+ < / button > {/ * use the bound action handler * /}
                    years old.
                </li>
            </div>
        )
    }
}

This mode can unify the action processing in a component level. If the view meaning of multi instance components is not clear and universal, such as the button component encapsulated by itself, this mode is the best. However, if the meaning of multi instance component is obvious and not universal, especially when it is used to display array elements, this mode will cause unnecessary rendering process.

Open the highlight updates option of react dev tools and click several timespush petAfter adding some elements, click+or-Button to see how the component is re rendered

It can be found that when we only modify the internal value of an array element (PET [x]. Age), other array elements will also be re rendered. This is because Pet.props.addAge And Pet.props.reduceAge It is an anonymous object that will be regenerated every time the parent component petsview is rendered. Purecomponent considers that the data on which the component depends has been updated, so it triggers re rendering. Although using React.Component This problem can be solved manually with the self-defined shouldcomponentupdate life cycle function. However, each time the parent component petsview is rendered, the attribute value of the anonymous child component is generated again, which also consumes computing resources.

Traditional scheme 2: implementation of sub components combined with index

The parent component passes the index value to the child component: in this mode, the parent component passes the index value to the child component, and implements its own event processing logic inside the child component, as follows:

class PetsView extends PureComponent {
    ...
    render() {
        ...
        return (
            <div style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={pet.id} 
                            Index = {index} // pass the index value directly to the child component
                        />)
                }
                ...
            </div>
        )
    }
}
class PetView extends PureComponent {

    //Implementation of action logic in subcomponents

    //Pass index on call
    addAge(index){
        state.pets[index].age += 1
    }

    //Or the function itself gets the index from props
    Reduceage = () = > {// function uses this object internally. It is more convenient to use XXX = () = > {...} to define component properties
        state.pets[this.props.index].age -= 1
    }

    render() {
        /** @type {initState['pets'][0]} */
        let state = this.props.state;
        let index = this.props.index;
        return (
            <div >
                <li> {state.name}: 
                    <button onClick={() =>  this.reduceAge (index)} > - < / button > {/ * passing index value * /} with closure
                    {state.age} 
                    <button onClick={ this.addAge }>+ < / button > {/ * or let the function implement itself to get the index value * /}
                    years old.
                </li>
            </div>
        )
    }
}

This mode enables the sub component to obtain index and process its own action logic, and the sub component can also display its own serial number, which has strong flexibility. Let’s take a look at the re rendering of the component when the internal state of the element changes

We find that array element components can be rendered well on demand, and this method has high efficiency in the case of rendering array elements.

However, because the internal operation function of the element component is boundUnique locationState operation logic, such asaddAge(index){ state.pets[index].age += 1}. Let’s say we have morestate.childrenArray, array element format andstate.petsSimilarly, when we want to use the same element component to display and operate these two arrays at the same time, this array rendering mode is not applicable. We can use the first scheme to meet the requirements of this situation, but the first scheme is not perfect in rendering efficiency.

Pastate array element operation scheme

Each node of imstate of pastate has node location information and store destination information. We can use this to operate array elements!

Pastate scheme 1: getting responsive nodes for

We use the getresponsestate function to obtain the responsive state of imstate for, as follows:

class PetsView extends PureComponent {
    ...
    render() {
        ...
        return (
            <div style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={ pet.id }{/ * note that you don't need to pass the index value here unless you want to use it for other purposes * /}
                        />)
                }
                ...
            </div>
        )
    }
}
import {..., getResponsiveState } from 'pastate'

class PetView extends PureComponent {
    addAge = () => {
        /** @type {initState['pets'][0]} */
        let pet = getResponsiveState( this.props.state ); // use getresponsestate to get the responsive state node
        pet.age += 1
    }
    reduceAge = () => {
        /** @type {initState['pets'][0]} */
        let pet = getResponsiveState( this.props.state ); // use getresponsestate to get the responsive state node
        pet.age -= 1
    }
    render() {
        /** @type {initState['pets'][0]} */
        let state = this.props.state;
        return (
            <div >
                <li> {state.name}: 
                    <button onClick={this.reduceAge}> - </button>
                    {state.age} 
                    <button onClick={this.addAge}> + </button>
                    years old.
                </li>
            </div>
        )
    }
}

We can see that the subcomponent gets the current props.state The corresponding responsive state, so that you can directly copy and modify the state, you don’t need to know props.state What is it store.state What node on the Internet! This mode enables reusable components to be used in multiple arrays with different mounting positions, and ensures good rendering performance

Pastate scheme 2: using imstate operation function

Pastate provides three functions that directly operate imstate, namelyset, merge, update. Let’s show you how to use these functions insteadgetResponsiveStateRealize the above function of operating pet age:

import {..., set, merge, update } from 'pastate'

class PetView extends PureComponent {
    addAge = () => {
        set(this.props.state.age, this.props.state.age + 1); 
    }
    reduceAge = () => {
        merge(this.props.state, {
            age: this.props.state.age - 1
        });
    }
    reduceAge_1 = () => {
        update(this.props.state.age, a => a - 1);
    }
    ...
}

It can be seen that the mode of imstate operation function is also very simple!

Note for using pastate array element operation scheme: when the value of the state node of the operation is null or undefined, it can only be usedmergeFunction merges the new value into the parent node, which cannot be usedgetResponsiveStatesetorupdate. When we design the state structure, we should try to avoid using absolute null value, we can use it'', []And so on.

In the next chapter, let’s look at how to render and process form elements in pastate.