本篇文章给大家分享的内容是关于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['pets'][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 渲染数组的模式一样,页面结果如下:
修改数组首先,我们想添加或减少数组元素,这用 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 (
<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>
)
}
}
非常容易!我们还添加了两个按钮并指定了点击处理函数,运行体验一下:
打开 react dev tools 的 highlight updates 选项,并点击 push 或 pop 按钮,可以观察到视图更新情况如我们所愿:
空初始数组与编辑器 intellisence通常情况下,数组节点的初始值是空的。为了实现编辑器 intellisence, 我们可以在外面定义一个元素类型,并注释这个数组节点的元素为该类型:
const initstate = {
...
/** @type {[pet]} */
pets: []
}
const pet = {
id: 'id01',
name: 'kitty',
age: 2
}
你也可以使用泛型的格式来定义数组类型: /** @type {array<pet>} */ 。
多实例组件的内部动作处理上一章我们提到了单实例组件,是指组件只被使用一次;而我们可以到 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 (
<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['pets'][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 增加一些元素后,再点击 + 或 - 按钮看看组件重新渲染的情况:
可以发现当我们只修改某一个数组元素内部的值(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['pets'][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 改变时,组件的重新渲染情况:
我们发现,数组元素组件可以很好地按需渲染,在渲染数组元素的情况下这种方法具有较高的运行效率。
但是,由于元素组件内部操作函数绑定了唯一位置的 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 '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 (
<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 方案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 到父节点中,不可以使用 getresponsivestate ,set 或 update。我们在设计 state 结构时,应尽量避免使用绝对空值,我们完全可以用 '', [] 等代替绝对空值。
下一章,我们来看看如何在 pastate 中渲染和处理表单元素。
这是 pastate.js 响应式 react state 管理框架系列教程的第三章,欢迎关注,持续更新。
这一章我们来看看在 pastate 中如何渲染和处理 state 中的数组。
相关推荐:
pastate.js 之响应式 react state 管理框架
pastate.js 响应式框架之多组件应用
以上就是pastate.js 响应式框架之数组渲染与操作 的详细内容。