资讯专栏INFORMATION COLUMN

【教程】Pastate.js 响应式框架(三)数组渲染与操作

linkin / 887人阅读

摘要:但是如果多实例组件的含义明显不具有通用性,特别是用于显示数组元素的情况下,使用这种模式会引发多余的渲染过程。假设我们还有数组,数组元素的格式与一样我们要用相同的元素组件来同时显示和操作这两个数组时,这种数组渲染模式就不适用了。

这是 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 (
            
My pets:
{state.map(pet => )}
) } }
class PetView extends PureComponent {
    render() {
        /** @type {initState["pets"][0]} */
        let state = this.props.state;
        return (
            
  • {state.name}: {state.age} years old.
  • ) } }

    这里定义了两个组件,第一个是 PetsView,用来显示 pets 数组; 第二个是 PetView,用来显示 pet 元素。
    接下来把 PetsView 组件放入 AppView 组件中显示:

    ...
    class AppView extends PureComponent {
        render() {
            /** @type {initState} */
            let state = this.props.state;
            return (
                
    ) } } ...

    完成!我们成功渲染了一个数组对象,这与用原生 react 渲染数组的模式一样,页面结果如下:

    修改数组

    首先,我们想添加或减少数组元素,这用 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() + "",
                name: "Puppy",
                age: 1
            })
        }
    
        popPet(){
            state.pets.pop()
        }
    
        render() {
            /** @type {initState["pets"]} */
            let state = this.props.state;
            return (
                
    My pets:
    {state.map(pet => )}
    ) } }

    非常容易!我们还添加了两个按钮并指定了点击处理函数,运行体验一下:

    打开 react dev tools 的 Highlight Updates 选项,并点击 push 或 pop 按钮,可以观察到视图更新情况如我们所愿:

    空初始数组与编辑器 intelliSence

    通常情况下,数组节点的初始值是空的。为了实现编辑器 intelliSence, 我们可以在外面定义一个元素类型,并注释这个数组节点的元素为该类型:

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

    你也可以使用泛型的格式来定义数组类型: /** @type {Array} */

    多实例组件的内部动作处理

    上一章我们提到了单实例组件,是指组件只被使用一次;而我们可以到 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["pets"]} */
            let state = this.props.state;
            return (
                
    ... { state.map((pet, index) => this.addAge(index)} // 绑定 index 值,传递给子组件 reduceAge={() => this.reduceAge(index)} // 绑定 index 值,传递给子组件 />) } ...
    ) } }
    class PetView extends PureComponent {
        render() {
            /** @type {initState["pets"][0]} */
            let state = this.props.state;
            return (
                
  • {state.name}: {/* 使用已绑定 index 值得动作处理函数 */} {state.age} {/* 使用已绑定 index 值得动作处理函数 */} years old.
  • ) } }

    这种模式可以把动作的处理统一在一个组件层级,如果多实例组件的视图含义不明确、具有通用性,如自己封装的 Button 组件等,使用这种动作处理模式是最好的。但是如果多实例组件的含义明显、不具有通用性,特别是用于显示数组元素的情况下,使用这种模式会引发多余的渲染过程。

    打开 react dev tools 的 Highlight Updates 选项,点击几次 push pet 增加一些元素后,再点击 +- 按钮看看组件重新渲染的情况:

    可以发现当我们只修改某一个数组元素内部的值(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 (
                
    ... { state.map((pet, index) => ) } ...
    ) } }
    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["pets"][0]} */
            let state = this.props.state;
            let index = this.props.index;
            return (
                
  • {state.name}: {/* 使用闭包传递 index 值 */} {state.age} {/* 或让函数实现自己去获取index值 */} years old.
  • ) } }

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

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

    但是,由于元素组件内部操作函数绑定了唯一位置的 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 (
                
    ... { state.map((pet, index) => ) } ...
    ) } }
    import {..., getResponsiveState } from "pastate"
    
    class PetView extends PureComponent {
        addAge = () => {
            /** @type {initState["pets"][0]} */
            let pet = getResponsiveState(this.props.state); // 使用 getResponsiveState 获取响应式 state 节点
            pet.age += 1
        }
        reduceAge = () => {
            /** @type {initState["pets"][0]} */
            let pet = getResponsiveState(this.props.state); // 使用 getResponsiveState 获取响应式 state 节点
            pet.age -= 1
        }
        render() {
            /** @type {initState["pets"][0]} */
            let state = this.props.state;
            return (
                
  • {state.name}: {state.age} years old.
  • ) } }

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

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

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

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

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

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

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

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/93803.html

    相关文章

    • 教程Pastate.js 响应框架(二)多组件应用

      摘要:这一章,我们在上一章的结构中添加多一些信息,并用多个组件来组织应用。是的响应式影子可以对任何节点进行直接赋值修改,会把修改结果作用到,并异步触发视图更新。因此在中的是对象而在中的是对象。 这是 pastate 系列教程的第二章,欢迎关注,持续更新。 这一章,我们在上一章的 state 结构中添加多一些信息,并用多个组件来组织 pastate 应用。 更新 state 结构 我们把上一章...

      Leo_chen 评论0 收藏0
    • Pastate.js : 响应 react state 管理框架

      摘要:简介是什么是一个响应式管理框架,实现了对的异步响应式管理。可靠性已经通过个测试用例的全面测试,稳定可靠。安装是一个状态管理框架,需要配合使用。 Pastate 简介 Pastate 是什么 Pastate 是一个响应式 react state 管理框架,实现了对 state 的异步响应式管理。Pastate 是一个精益框架,它对很多高级概念进行了友好封装,这意味着你不必学习一些难以理解...

      jonh_felix 评论0 收藏0
    • 实例讲解基于 React+Redux 的前端开发流程

      摘要:宅印前端基于的模式开发,我们指定了一套分工明确的并行开发流程。下面通过一个苹果篮子实例,来看看整个应用开发流程。容器负责接收中的和并发送大多数时候需要和直接连接,容器一般不需要多次使用,比如我们这个应用的苹果篮子。 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 star 数达 42000 +,超过了 jquery 的 39000+,...

      chaosx110 评论0 收藏0
    • 校招社招必备核心前端面试问题详细解答

      摘要:本文总结了前端老司机经常问题的一些问题并结合个人总结给出了比较详尽的答案。网易阿里腾讯校招社招必备知识点。此外还有网络线程,定时器任务线程,文件系统处理线程等等。线程核心是引擎。主线程和工作线程之间的通知机制叫做事件循环。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文总结了前端老司机经常问题的一些问题并结合个...

      DevTalking 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <