likes
comments
collection
share

React 我们只是数据的搬运工,从自然规律到设计,编码的一些发散

作者站长头像
站长
· 阅读数 52

React 状态管理

状态管理符合事物转换的一般规律

我们都知道大气中水循环,降水,冰雪融化等使地面形成河流,河流中的水因重力流向湖泊,海洋,蒸发形成云雨,受风力影响,云雨移动又形成降水,如此循环往复。React 组件中的数据流动其实跟这个水循环也是极其相似的,所以只要能理解这个水循环,那么 React 的数据流动就能比较轻易的明白。

我们来对比看看这个水循环大致模拟图与 React 数据流向图 :

React 我们只是数据的搬运工,从自然规律到设计,编码的一些发散

React 我们只是数据的搬运工,从自然规律到设计,编码的一些发散

我们从这两个结构图中,能感受到这个过程是否极其相似,那么就可以把蒸发,风力看成事件属性,useState看成雨云冷凝形成降水,这不是巧合,这符合事物转换的一般自然规律。你们想想水从气态到液态再到固态,再反向转换是不是也是这个道理。

有了这样的想法,我们来看看代码的实现,就更清楚了,还是延用之前用到的周计划的项目,之前的项目是只是一个静态展示,现在用到状态管理让数据动起来。

创建一个可操作的周计划

  • 创建 weekPlanList.json 数据源:
[
  {
    "title": "周一的计划",
    "index": "1",
    "todoItems": [
      {
        "index": "1-1",
        "time": "7:00 PM",
        "matters": "运动健身"
      },
      {
        "index": "1-2",
        "time": "9:00 PM",
        "matters": "阅读"
      }
    ]
  },
  {
    "title": "周二的计划",
    "index": "2",
    "todoItems": [
      {
        "index": "2-1",
        "time": "7:00 PM",
        "matters": "练习萨克斯"
      },
      {
        "index": "2-2",
        "time": "8:00 PM",
        "matters": "看一部电影"
      }
    ]
  },
  {
    "title": "周三的计划",
    "index": "3",
    "todoItems": [
      {
        "index": "3-1",
        "time": "7:00 PM",
        "matters": "练习毛笔字"
      },
      {
        "index": "3-2",
        "time": "8:00 PM",
        "matters": "短距离夜骑"
      }
    ]
  },
  {
    "title": "周四的计划",
    "index": "4",
    "todoItems": [
      {
        "index": "4-1",
        "time": "7:00 PM",
        "matters": "运动健身"
      },
      {
        "index": "4-2",
        "time": "8:00 PM",
        "matters": "高数学习"
      }
    ]
  },
  {
    "title": "周五的计划",
    "index": "5",
    "todoItems": [
      {
        "index": "5-1",
        "time": "7:00 PM",
        "matters": "云顶之奕"
      }
    ]
  },
  {
    "title": "周六的计划",
    "index": "6",
    "todoItems": [
      {
        "index": "6-1",
        "time": "7:00 AM",
        "matters": "绿道骑行"
      },
      {
        "index": "6-2",
        "time": "7:00 PM",
        "matters": "线代学习"
      }
    ]
  },
  {
    "title": "周日的计划",
    "index": "7",
    "todoItems": [
      {
        "index": "7-1",
        "time": "8:00 AM",
        "matters": "学习,整理,总结"
      },
      {
        "index": "7-2",
        "time": "3:00 PM",
        "matters": "追番,追剧,追综艺"
      }
    ]
  }
]
  • 创建 TodoItem.js 组件:
/**
 * TodoItem.js
 * 每天待处理项
 * @param {*} param0 
 * @returns 
 */

function TodoItem ({matters, time, checked, index, checkChange = f => f}) {
  return(
    <div className="check-item">
      <label htmlFor="input">{ `${time} : ${matters}` }</label>
      <input
        type="checkbox"
        checked={checked}
        value={index}
        style={{marginLeft: '6px'}}
        onChange={(e) => {checkChange(e.target.value)}} 
       />
    </div>
  )
}

export default TodoItem;
  • 创建 TodoItems.js 组件:
/**
 * TodoItems.js
 * 每天待处理项列表
 * @param {*} param0 
 * @returns 
 */

import TodoItem from "./TodoItem";

function TodoItems ({todoItems, checkedIndexList, checkChange = f => f}) {
  return (
    <ul>
      {todoItems.map(item => (
         <li key={item.index} >
           <TodoItem {...item} checked={checkedIndexList.includes(item.index)} checkChange={checkChange} />
         </li>
      ))}
    </ul>
  )
}

export default TodoItems;
  • 创建 WeekList.js 组件:
/**
 * WeekList.js
 */

import React from 'react';
import TodoItems from "./TodoItems";

function WeekList({weekList, checkedIndexList, checkChange}) {
  return weekList.map((week, index) => (
    <React.Fragment key={index}>
      <div className="week-title">{ week.title }</div>
      <div className="items-container">
        <TodoItems {...week} checkedIndexList={checkedIndexList} checkChange={checkChange} />
      </div>
    </React.Fragment>
  ))
} 

export default WeekList;
  • 创建 App.js 组件:
import { useState } from 'react';
import WeekList from './components/WeekList';
import weekplanList from './source/weekPlanList.json';

import './App.css';

const weekPlans = [...weekplanList];

function App() {
  const [checkedIndexList, setCheckedIndexList] = useState([]);

  const checkChange = (val) => {
    let checkedList = checkedIndexList.filter(checkedIndex => checkedIndex !== val);
    if (checkedList.length === checkedIndexList.length) {
      checkedList = [...checkedList, val];
    }
    setCheckedIndexList(checkedList);
  }
  return (
    <div className="App">
      <div className="plan-head">周计划管理</div>
      <div className="plan-body">
        <WeekList weekList={weekPlans} checkedIndexList={checkedIndexList} checkChange={checkChange}></WeekList>
      </div>
    </div>
  );
}

export default App;

经过如上代码的编写,我们可以得到如下视图以及交互:

React 我们只是数据的搬运工,从自然规律到设计,编码的一些发散

周计划代码解读

先说说我最开始创建这个项目的数据结构是怎样的吧?最开始,考虑到为每一个代办事项添加一个 checked 属性,然后绑定对应的 checked 属性,最开始我也是这么做的,如果这么做会有什么样的结果呢?我们来看一下:

TodoItem.js 文件中,那么checked 属性绑定对应的 checked,但是我们在变更事件 onChange 中,要让 App 组件中知道是哪个代办项发生了变化,那么我们就必须要把待办项 唯一键 index 属性, 以及当天的 index 属性回传,达到通知 useState 进行具体的改变,这样我们需要历遍 weekPlans 改变才能改变代办项的 checked 状态。这样的设计,有没有发现什么弊端呢?

  • 回传参数复杂,还需要历遍去找当天的唯一键 index
  • 历遍数据去更改数据源以达到更新 DOM 的效果
  • 加上更新 DOM 的历遍,我们在重复的去做历遍操作

或许这个时候我们就在想,这样设计这个组件是否恰当,我们再来分析一下现在的这种数据模式。

  • 更新选中的 index 的值,可以做及时的更新
  • 重新渲染 DOM 的过程中,借助渲染 DOM 的历遍来进行数据处理,不需要进行多次的历遍
  • 不会污染源数据,只针对选择的 index 进行处理
  • 回传参数只需要找对当前处理的待办项 index 进行简单的增删操作

像这样的模式,其实我们在开发中也会经常遇到,像列表数据的选择,树形下拉菜单的选择等等,很多模式都是利用的这个。

大概的设计已经清楚了,我们来看看这个 useState 的作用:

const  [value, setValue] = useState(initValue);
  1. useState 函数接受一个初始值参数,返回一个数组;
  2. 数组的第一个元素 value 就是需要交互发生改变的值,初始化的时候 initValue 会被赋值给 value;
  3. 第二个元素 setValue 方法就是通过函数处理返回一个值,这个值作为第一个元素 value 的值。

值得注意的是,我们在处理数据变化的时候(使用 useState 的时候),我们是在根组件(App.js)中,为什么不能是在 WeekList 或是其他中间组件,如果一整条河被污染了那我们首先应该检测,处理的是不是源头,是不是也是这样依次往下进行排查?一句话就是,把状态改变放在数据源头位置,有利于我们对问题的排查,也有利于上级组件需要使用的时候再返工去给上级设置状态,数据流动性思维很重要

如果你学习过 Vue,这样的描述,我们是不是很容易联想到 Vue 的 computed 计算属性中的 get,set 方法,那这样就更好理解了。插一句,我们在学习的过程中,学习到新的知识点的时候,要多思考现在的这个知识点跟原来熟悉,学过的知识点的一些关联,对比,相似等特性,这样对我们的理解,记忆,使用都会有很多的帮助,学习是先扩散再聚拢的。

在 App 组件中有这样一段代码我觉得可以拿来讲解一下,并不是说这代码很厉害,很不同寻常,我只是想让大家在写代码的时候要多思考。

const checkChange = (val) => {
    let checkedList = checkedIndexList.filter(checkedIndex => checkedIndex !== val);
    if (checkedList.length === checkedIndexList.length) {
      checkedList = [...checkedList, val];
    }
    setCheckedIndexList(checkedList);
}

这段代码也就是在对选择的待办项的唯一键 index 进行增、删的操作。想必很多朋友在写这个代码的时候的设计思路如下:

先在 checkedList 中判断是否存在当前选择的项,如果存在做删除操作,如果不存在,则做新增操作;

这样思考肯定没有问题,我反而觉得你逻辑非常严谨,可以说是密不透风,我们来对比一下代码看看呢?

const checkChange = (val) => {
    const hasIndex = checkedIndexList.includes(val);
    let checkedList = [];
    if (hasIndex) {
        const index = checkedIndexList.findIndex(checkedIndex => checkedIndex === val);
        // 友情提醒:splice 方法会改变原数组,所以很多时候用 filter 更好一些
        checkedList = [...checkedIndexList];
        checkedList.splice(index, 1);
    } else {
        checkedIndexList = [...checkedIndexList, val];
    }
}

对比于 App 组件中的代码,跟我们平常写的差别是不是一下就出来了,App 组件中,先不判断是否存在当前处理的 index,而是直接删除,再比较删除前后的数组的长度来判断之前的操作是否为删除操作,如果长度相同,那么之前的操作就可以看做是一个赋值操作,再进行一个新增操作,搞定,如果长度不同,那么刚才的操作肯定就是一个删除操作,那么数据处理直接结束。当我们看到这两种模式的时候,是否能联想起刚学 JavaScript 时的一对循环,(while...do... 和 do...while);

总结

  1. 通过一个大气水循环去理解 React 中数据流方式,把父组件看做高海拔,把数据看做水,那么 React 数据流也符合一般自然规律
  2. 通过一个计划勾选来理解 useState 在 React 中对数据的控制,数据流动性思维对 useState 使用在什么位置提供了一个使用的依据;
  3. 通过一个案例分析,来思考推导出组件的设计,模式的设计对于开发,学习的思维的发散,学习要关联,对比,先发散再聚合,编码要思考如何设计更优更简性能更好的代码;
转载自:https://juejin.cn/post/7270061728897007674
评论
请登录