首頁  >  文章  >  web前端  >  Pastate.js 響應式框架之數組渲染與操作

Pastate.js 響應式框架之數組渲染與操作

不言
不言原創
2018-04-10 16:24:551612瀏覽

這篇文章給大家分享的內容是關於Pastate.js 響應式框架之數組渲染與操作 ,有著一定的參考價值,有需要的朋友可以參考一下

這是Pastate.js 響應式react state 管理框架系列教學的第三章,歡迎關注,持續更新。

這一章讓我們來看看在 pastate 中如何渲染和處理 state 中的陣列。

渲染數組

首先我們更新一下state 的結構:

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

我們定義了一個有物件元素構成的陣列initState.pets, 且該陣列有一個初始元素。

接著,我們定義相關元件來顯示pets 的值:

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

這裡定義了兩個元件,第一個是PetsView,用來顯示pets 陣列; 第二個是PetView,用來顯示pet 元素。  
接下來把 PetsView 元件放入 AppView 元件中顯示:

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

完成!我們成功渲染了一個數組對象,這與用原生react 渲染數組的模式一樣,頁面結果如下:

Pastate.js 響應式框架之數組渲染與操作

修改數組

首先,我們想要新增或減少陣列元素,這用pasate 實作起來非常簡單。受 vue.js 啟發,pastate 對 store.state 的陣列節點的以下7個 陣列變異方法 都進行了加強,你可以直接呼叫這些陣列函數,pastate 會自動觸發視圖的更新。這7 個陣列變異方法如下

  • push()

  • pop()

  • shift()

  • #unshift()

  • ##splice()

  • sort()

  • reverse( )

我們來嘗試使用push 和pop 來更新陣列:

class PetsView extends PureComponent {

    pushPet(){
        state.pets.push({
            id: Date.now() + &#39;&#39;,
            name: &#39;Puppy&#39;,
            age: 1
        })
    }

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

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

非常容易!我們還新增了兩個按鈕並指定了點擊處理函數,執行體驗:

Pastate.js 響應式框架之數組渲染與操作

#開啟react dev tools 的Highlight Updates 選項,並點擊push 或pop 按鈕,可以觀察到視圖更新情況如我們所願:

Pastate.js 響應式框架之數組渲染與操作

#空初始數組與編輯器intelliSence

通常情況下,數組節點的初始值是空的。為了實作編輯器intelliSence, 我們可以在外面定義一個

元素類型,並註解這個陣列節點的元素為該型別:

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

你也可以使用泛型的格式來定義數組類型:  

/**@type {數組}*/

多重實例元件的內部動作處理

上一章我們提到了

單一實例元件,是指元件只被使用一次;而我們可以到 PetView 被用於顯示數組元素,會被多次使用。我們把這類在多處被使用的元件稱為多重實例元件。多實例元件內部動作的處理邏輯由元件實例的具體位置而定,與單一實例元件的處理模式有差別,我們來看看。

我們試著製作一個每個寵物視圖中加入兩個按鈕來調整寵物的年齡,我們用兩種傳統方案和pastate方案分別實現:

react 傳統方案

傳統方案1:父元件處理

父元件向子元件傳遞綁定index的處理函數:這個模式是把子元件的動作處理邏輯實作在父元件中,然後父元件把動作綁定對應的index 後傳遞給子元件

class PetsView extends PureComponent {
    ...
    addAge(index){
        state.pets[index].age += 1
    }
    reduceAge(index){
        state.pets[index].age -= 1
    }
    render() {
        /** @type {initState[&#39;pets&#39;]} */
        let state = this.props.state;
        return (
            <p style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={pet.id} 
                            addAge={() => this.addAge(index)} // 绑定 index 值,传递给子组件
                            reduceAge={() => this.reduceAge(index)} //  绑定 index 值,传递给子组件
                        />)
                }
                ...
            </p>
        )
    }
}
class PetView extends PureComponent {
    render() {
        /** @type {initState[&#39;pets&#39;][0]} */
        let state = this.props.state;
        return (
            <p >
                <li> {state.name}: 
                    <button onClick={this.props.reduceAge}> - </button> {/* 使用已绑定 index 值得动作处理函数 */}
                    {state.age} 
                    <button onClick={this.props.addAge}> + </button> {/* 使用已绑定 index 值得动作处理函数 */}
                    years old.
                </li>
            </p>
        )
    }
}

這種模式可以把動作的處理統一在一個元件層級,如果多實例元件的視圖意義不明確、具有通用性,如自己封裝的Button 元件等,使用這種動作處理模式是最好的。但是如果多實例元件的含義明顯、不具有通用性,特別是用於顯示數組元素的情況下,使用這種模式會引發多餘的渲染過程。

開啟react dev tools 的Highlight Updates 選項,點擊幾次

push pet 增加一些元素後,再點擊 - 按鈕看看元件重新渲染的情況:

Pastate.js 響應式框架之數組渲染與操作

可以發現當我們只修改某一個陣列元素內部的值(pet[x].age)時,其他數組元素也會被重新渲染。這是因為 Pet.props.addAge 和 Pet.props.reduceAge 是每次父元件 PetsView 渲染時都會重新產生的匿名對象,PureComponent 以此認為元件依賴的資料更新了,所以觸發重新渲染。雖然使用 React.Component 配合 自訂的 shouldComponentUpdate 生命週期函數可以手動解決這個問題,但每次渲染父元件 PetsView 時都會重新產生一次匿名子元件屬性值,也在消耗運算資源。

传统方案2:子组件结合 index 实现

父组件向子组件传递 index 值:这种模式是父组件向子组件传递 index 值,并在子组件内部实现自身的事件处理逻辑,如下:

class PetsView extends PureComponent {
    ...
    render() {
        ...
        return (
            <p style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={pet.id} 
                            index={index} // 直接把 index 值传递给子组件
                        />)
                }
                ...
            </p>
        )
    }
}
class PetView extends PureComponent {

    // 在子组件实现动作逻辑

    // 调用时传递 index
    addAge(index){
        state.pets[index].age += 1
    }

    // 或函数自行从 props 获取 index
    reduceAge = () => { // 函数内部使用到 this 对象,使用 xxx = () => {...} 来定义组件属性更方便
        state.pets[this.props.index].age -= 1
    }

    render() {
        /** @type {initState[&#39;pets&#39;][0]} */
        let state = this.props.state;
        let index = this.props.index;
        return (
            <p >
                <li> {state.name}: 
                    <button onClick={() => this.reduceAge(index)}> - </button> {/* 使用闭包传递 index 值 */}
                    {state.age} 
                    <button onClick={this.addAge}> + </button> {/* 或让函数实现自己去获取index值 */}
                    years old.
                </li>
            </p>
        )
    }
}

这种模式可以使子组件获取 index 并处理自身的动作逻辑,而且子组件也可以把自身所在的序号显示出来,具有较强的灵活性。我们再来看看其当元素内部 state 改变时,组件的重新渲染情况:

Pastate.js 響應式框架之數組渲染與操作

我们发现,数组元素组件可以很好地按需渲染,在渲染数组元素的情况下这种方法具有较高的运行效率。

但是,由于元素组件内部操作函数绑定了唯一位置的 state 操作逻辑,如addAge(index){ state.pets[index].age += 1}。假设我们还有 state.children 数组,数组元素的格式与 state.pets 一样, 我们要用相同的元素组件来同时显示和操作这两个数组时,这种数组渲染模式就不适用了。我们可以用第1种方案实现这种情况的需求,但第1种方案在渲染效率上不是很完美。

pastate 数组元素操作方案

Pastate 的 imState 的每个节点本身带有节点位置的信息和 store 归宿信息,我们可以利用这一点来操作数组元素!

pastate 方案1:获取对于的响应式节点

我们使用 getResponsiveState 函数获取 imState 对于的响应式 state,如下:

class PetsView extends PureComponent {
    ...
    render() {
        ...
        return (
            <p style={{ padding: 10, margin: 10 }}>
                ...
                {
                    state.map((pet, index) => 
                        <PetView 
                            state={pet} 
                            key={pet.id}   {/* 注意,这里无需传递 index 值,除非要在子组件中有其他用途*/}
                        />)
                }
                ...
            </p>
        )
    }
}
import {..., getResponsiveState } from &#39;pastate&#39;

class PetView extends PureComponent {
    addAge = () => {
        /** @type {initState[&#39;pets&#39;][0]} */
        let pet = getResponsiveState(this.props.state); // 使用 getResponsiveState 获取响应式 state 节点
        pet.age += 1
    }
    reduceAge = () => {
        /** @type {initState[&#39;pets&#39;][0]} */
        let pet = getResponsiveState(this.props.state); // 使用 getResponsiveState 获取响应式 state 节点
        pet.age -= 1
    }
    render() {
        /** @type {initState[&#39;pets&#39;][0]} */
        let state = this.props.state;
        return (
            <p >
                <li> {state.name}: 
                    <button onClick={this.reduceAge}> - </button>
                    {state.age} 
                    <button onClick={this.addAge}> + </button>
                    years old.
                </li>
            </p>
        )
    }
}

我们可以看到,子组件通过 getResponsiveState 获取到当前的 props.state 对应的响应式 state,从而可以直接对 state 进行复制修改,你无需知道 props.state 究竟在 store.state 的什么节点上! 这种模式使得复用组件可以在多个不同挂载位置的数组中使用,而且可以保证很好的渲染性能:

Pastate.js 響應式框架之數組渲染與操作

pastate 方案2:使用 imState 操作函数

Pastate 提供个三个直接操作 imState 的函数,分别为 set, merge, update。我们来演示用这些操作函数来代替  getResponsiveState 实现上面操作宠物年龄的功能:

import {..., set, merge, update } from &#39;pastate&#39;

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);
    }
    ...
}

可见,这种 imState 操作函数的模式也非常简单!

使用 pastate 数组元素操作方案的注意事项:当操作的 state 节点的值为 null 或 undefined 时,  只能使用 merge 函数把新值 merge 到父节点中,不可以使用  getResponsiveStatesetupdate。我们在设计 state 结构时,应尽量避免使用绝对空值,我们完全可以用 '', [] 等代替绝对空值。

下一章,我们来看看如何在 pastate 中渲染和处理表单元素。


这是 Pastate.js 响应式 react state 管理框架系列教程的第三章,欢迎关注,持续更新。

这一章我们来看看在 pastate 中如何渲染和处理 state 中的数组。

相关推荐:

Pastate.js 之响应式 react state 管理框架

Pastate.js 响应式框架之多组件应用

以上是Pastate.js 響應式框架之數組渲染與操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn