本篇文章给大家分享的内容是关于Pastate.js 响应式框架之多组件应用 ,有着一定的参考价值,有需要的朋友可以参考一下

这是 pastate 系列教程的第二章,欢迎关注,持续更新。

这一章,我们在上一章的 state 结构中添加多一些信息,并用多个组件来组织 pastate 应用。

更新 state 结构

我们把上一章的个人基本信息数据包装为 state.basicInfo 属性的对象,并向 state 中添加 address 属性,保存个人地址信息:

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

由于 JavaScript 语言的限制,pastate 不能检测到通过赋值来为对象添加新属性,以自动把新属性转化为响应式节点。所以你应该在 initState 中把需要用到的 state 属性都定义出来,把属性值初始化为 null 或空数组都是可以的。下面是个错误的例子

const initState = { basicInfo: ..., address: ... } const store = new Pastore(initState) const state = store.state state.hobby = 'coding' // 错误,state.hobby 属性不具有受 pastate 控制,不具有响应式特点

即使支持这种特性,它也会使开发者难以完全把握 state 的结构,导致应用难以开发和维护,所以我们应该在 initState 里对 state 的结构进行完整的定义。

分别开发 basicInfo 和 address 的视图组件

我们先使用一种简单临时的方式来构建子组件:

... /** @type {initState} */ const state = store.state; class BasicInfoView extends Component { render(){ return ( <p 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/> </p> ) } }class AddressView extends Component { render(){ return ( <p style={{padding: 10, margin: 10}}> <strong>Address:</strong><br/> My country is {state.address.country}.<br/> My city is {state.address.city}.<br/> </p> ) } }

可以看到,BasicInfoView 组件直接引用 store.state.basicInfo 的值,AddressView 组件直接引用 store.state.address 的值。接着修改原来的 AppView 父组件,把这两个子组件嵌套进去,同时增加一个方法来修改 address.city 的值:

... class AppView extends Component { increaseAge(){ state.basicInfo.age += 1 } decreaseAge(){ state.basicInfo.age -= 1 } changeCity(){ state.address.city += '!' } render() { return ( <p 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> </p> ) } } ...

完成!让我们运行一下:

点击按钮,看起来一切正常!我们通过 Chrome 的 react dev tools 来观察一下当 state 改变时,各个组件的渲染情况。打开浏览器的开发者工具,选择 react 标签,勾选上 Highlight Updates, 这时当组件重新渲染时,会被带颜色的方框框起来。

我们点击页面上 decrease age 按钮试试,组件重新渲染的结果如下:

我们可以发现,当只有 state.basicInfo.age 更改时,AppView、BasicInfoView 和 AddressView 3个组件都会被重新渲染,即使 AddressView 所引用的数据没有发生任何改变!这是 react 多组件渲染的通常情况,当应用组件简单、嵌套层级不多时,我们不会感觉到这种模式会带来什么明显的影响;但是当应用组件的嵌套关系变得比较复杂的时候,会带来性能隐患,我们需要来关注这个问题。

store.imState 与 store.state

先介绍一下 store 中的两个不同的 state:store.imStatestore.state ,你可以尝试了解一下:

store.imState 是应用状态的数据实体,它被 pastate 使用 immutable 的机制进行管理,当节点的内容更新时,该节点的所有祖先的“引用”都会被更新。imState 的每个节点值除了 null 或 undefined 外,都是包装类型(String, Number, Boolean, Object, Array)。

store.state 是 store.imState 的 响应式影子, 可以对 store.state 任何节点进行直接赋值修改,pastate 会把修改结果作用到 store.imState,并异步触发视图更新。

或者简化为以下两点:

store.imState 用来渲染视图

store.state 用来操作数据

这两个概念对于没有使用过 redux 和没了解过 vue.js 原理的人来说可能有点难以理解。不过没关系,不理解这两个概念并不妨碍你使用 pastate,你可以在使用 pastate 的过程中完全感觉不到 imState 的存在。pastate 的理念就是封装复杂概念,让你可以用一种简单的方式去实现复杂的功能。

如果你想要理解 pastate 的详细原理,可以查看原理章节。

使用 props 接收 imState,实现组件的按需渲染

当一个 component 与 store 连接时,store 会把 imState 传递到 component 的 props
.state 中,因此我们可以在 AppView 组件的 props 中接收 state,同时把 AppView 组件的基类改为 react 纯组件 PureComponent,这样就开启了组件按需渲染效果:

import React, { PureComponent } from 'react'; // 1. 改用 PureComponent 代替 Component ... class AppView extends PureComponent { // 1. 改用 PureComponent ... render() { /** @type {initState} */ let state = this.props.state; // 2. 从 props 接收 state return ( <p style={{padding: 10, margin: 10, display: "inline-block"}}> {/** 3. 把 state 的子节点传递给对于的子组件 */} <BasicInfoView state={state.basicInfo}/> <AddressView state={state.address}/> ... </p> ) } } ...