当前位置: 首页 > news >正文

【React系列】如何构建React应用程序

前面几篇内容简单整理了一些React的基础知识点,以方便大家能够简单的了解React的一些相关概念。在本节中,我们来试着以一个简单的例子来分析,如何构建一个React应用程序,该如何去思考。

首先,我们来选择比较常见的一个示例程序:TodoList来作为本次的分析案例。

图片描述

上面是一个简单的TodoList的的样子,此处并没有进行样式修饰,大家可以后面自己完善。我们将按照以下几个步骤进行分析设计:

步骤1:将 UI 拆解到组件层次结构中

当我们拿到一个UI设计时,需要我们将之进行拆解,使之成为由每个组件(和子组件)的结构构成的一个整体,并且可以根据功能给各个部分进行命名。

但是你该如何拆分组件呢?其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。

React最大的卖点是轻量组件化。我们分析一下以上截图中的页面,如果要分组件的话,我们大约可以分成一个总组件和两个子组件。一个输入内容的组件,一个显示内容列表(带删除功能)的组件,外面再用一个总组件将两个子组件包括起来。

图片描述

这样,我们的应用程序的结构就清晰了:

  • todoList --- 整个应用程序用例

    • typeNew --- 接收用户的输入
    • listTodo --- 显示所有的list item

步骤2: 用 React 构建一个静态版本

通过上面的结构划分,我们代码的整体结构大致如下:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';


// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todoList: []
    }
  }
  render() {
      return (
          <div>
              <TypeNew  />
              <ListTodo />
          </div>
      );
  }
};

// TypeNew 组件用于新增数据,
class TypeNew extends Component {
  render() {
      return (
          <form>
              <input type="text" placeholder="typing a newthing todo" autoComplete="off" />
          </form>
      );
  }
};

// ListTodo 组件用于展示列表,并可以删除某一项内容,
class ListTodo extends Component {
  render() {
      return (
          <ul id="todo-list">
              {/* 其中显示数据列表 */}
          </ul>
      );
  }
};

// 将 TodoList 组件渲染到页面
ReactDOM.render(<TodoList />, document.getElementById('root'));

目前为止你已经有了组件层次结构,现在是时候实现你的 app 了。最简单的方法是构建一个采用数据模型并渲染 UI 但没有交互性的版本。最好解耦这些处理,因为构建静态版本需要 大量的代码 和 少量的思考,而添加交互需要 大量思考 和 少量的代码。我们将看到原因。

要构建你 app 的一个静态版本,用于渲染数据模型, 您将需要构建复用其他组件并使用 props 传递数据的组件。props 是将数据从 父级组件 传递到 子级 的一种方式。如果你熟悉 state 的概念,在构建静态版本时 不要使用 state ** 。state 只用于交互,也就是说,数据可以随时被改变。由于这是一个静态版本 app,所以你并不需要使用 state 。

您可以 自上而下 或 自下而上 构建。也就是说,您可以从构建层次结构中顶端的组件开始(即从 FilterableProductTable 开始),也可以从构建层次结构中底层的组件开始(即 ProductRow )。在更简单的例子中,通常 自上而下 更容易,而在较大的项目中,自下而上,更有利于编写测试。

在这一步结束时,你已经有了一个可重用的组件库,用于渲染你的数据模型。组件将只有 render() 方法,因为这是你应用程序的静态版本。层次结构顶部的组件( FilterableProductTable )应该接收你的数据模型作为 prop 。如果您对基础数据模型进行更改,并再次调用 ReactDOM.render(),UI 将同步更新。这有利于观测UI的更新以及相关的数据变化,因为这中间没有做什么复杂的事情。React 的 单向数据流(也称为 单向绑定 )使所有模块化和高性能。

♥ 小插曲 区分: Props(属性) vs State(状态)

步骤3: 确定 UI state(状态) 的最小(但完整)表示

为了你的 UI 可以交互,你需要能够触发更改底层的数据模型。React 通过 state 使其变得容易。

要正确的构建应用程序,你首先需要考虑你的应用程序需要的可变 state(状态) 的最小集合。这里的关键是:不要重复你自己 (DRY,don't repeat yourself)。找出你的应用程序所需 state(状态) 的绝对最小表示,并且可以以此计算出你所需的所有其他数据内容。正如我们在构建一个 TODO 列表,只保留一个 TODO 元素数组即可;不需要为元素数量保留一个单独的 state(状态) 变量。相反,当你要渲染 TODO 计数时,只需要获取 TODO 数组的长度即可。

在我们的例子中,主要出现的数据有两个,一个是todo列表,一个是用户输入的新的todo项目,todo列表会根据用户的添加和删除发生变化,可以认为是属于state的,对于用户输入的todo项目,我们发现它只是一个中间的临时值,并不需要设置相应的变量进行存储,所以我们的最终的State是:

  • todo列表

步骤4:确定 state(状态) 的位置

既然是展示数据,首先要考虑数据存储在哪里,来自于哪里。现在这里放一句话——React提倡所有的数据都是由父组件来管理,通过props的形式传递给子组件来处理——先记住,接下来再解释这句话。

上面提到,做一个todolist页面需要一个父组件,两个子组件。父组件当然就是todolist的『总指挥』,两个子组件分别用来add和show、delete。用通俗的方式讲来,父组件就是领导,两个子组件就是协助领导开展工作的,一切的资源和调动资源的权利,都在领导层级,子组件配合领导工作,需要资源或者调动资源,只能申请领导的批准。

这么说来就明白了吧。数据完全由父组件来管理和控制,子组件用来显示、操作数据,得经过父组件的批准,即——父组件通过props的形式将数据传递给子组件,子组件拿到父组件传递过来的数据,再进行展示。

另外,根据React开发的规范,组件内部的数据由state控制,外部对内部传递数据时使用 props 。这么看来,针对父组件来说,要存储todolist的数据,那就是内部信息(本身就是自己可控的资源,而不是『领导』控制的资源),用state来存储即可。而父组件要将todolist数据传递给子组件,对子组件来说,那就是传递进来的外部信息(是『领导』的资源,交付给你来处理),需要使用props。

此时我们的代码可以修改为:

// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        todoList: []
      }
    }

    render() {
        return (
            <div>
                <TypeNew  />
                <ListTodo />
            </div>
        );
    };
};

现在,已经确定了应用所需 state(状态) 的最小集合。接下来,需要确定是哪个组件可变,或者说哪个组件拥有这些 state(状态) 。

记住:React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:

对于你应用中的每一个 state(状态) :

  • 确定每个基于这个 state(状态) 渲染的组件。
  • 找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。愚人码头注:父级组件)。
  • 公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。
  • 如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。

步骤5:添加反向数据流

目前,构建的应用已经具备了正确渲染 props(属性) 和 state(状态) 沿着层次结构向下传播的功能。现在是时候实现另一种数据流方式:层次结构中深层的 form(表单) 组件需要更新 TodoList中的 state(状态) 。

想想我们希望发生什么。我们期望当用户改变表单输入进行提交的时候,我们更新 state(状态) 来反映用户的输入。由于组件只能更新它们自己的 state(状态) ,TodoList将传递回调到 TypeNew,然后在 state(状态) 被更新的时候触发。我们可以使用表单的onSubmit事件来接收通知。而且通过 TodoList传递的回调调用 setState(),然后应用被更新。同理删除处理在ListTodo中触发按钮的点击onClick事件来调用TodoList传递的回掉函数来更新删除后的state。

最终我们的代码结果如下:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';


// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todoList: []
    }
    this.handleAddNewItem = this.handleAddNewItem.bind(this);
    this.handleDelItem = this.handleDelItem.bind(this);
  }

  handleAddNewItem(todo) {
    var todoList = this.state.todoList;
    if(todo === "") {
      return;
    }
    todoList.push(todo);
    this.setState({
      todoList: todoList
    })
  }

  handleDelItem(index) {
    var todoList = this.state.todoList;
    todoList.splice(index, 1);
    this.setState({
      todoList: todoList
    })
  }

  render() {
      return (
          <div>
              <TypeNew typeNewItem={this.handleAddNewItem} />
              <ListTodo todoList={this.state.todoList} onDelItem={this.handleDelItem} />
          </div>
      );
  }
};

// TypeNew 组件用于新增数据,
class TypeNew extends Component {
  constructor(props){
    super(props);
    this.onHandleAddNewItem = this.onHandleAddNewItem.bind(this);
  }
  onHandleAddNewItem(e) {
    e.preventDefault();
    var inputDom = this.textInput;
    var newthing = inputDom.value.trim();
    this.props.typeNewItem(newthing);
    inputDom.value = '';
  }
  render() {
      return (
          <form onSubmit={this.onHandleAddNewItem}>
              <input type="text" ref={(input) => { this.textInput = input; }} placeholder="typing a newthing todo" autoComplete="off" />
          </form>
      );
  }
};

// ListTodo 组件用于展示列表,并可以删除某一项内容,
class ListTodo extends Component {
  constructor(props){
    super(props);
    this.onHandleDelItem = this.onHandleDelItem.bind(this);
  }

  onHandleDelItem(e){
    this.props.onDelItem(e.target.getAttribute("data-key"));
  }

  render() {
      return (
          <ul id="todo-list">
              {
                // this.props.todo 获取父组件传递过来的数据
                // {/* 遍历数据 */}
                this.props.todoList.map(function (item, i) {
                    return (
                        <li>
                            <label>{item}</label>
                            <button onClick={this.onHandleDelItem} data-key={i}>delete</button>
                        </li>                     
                      );
                }.bind(this))
              }
          </ul>
      );
  }
};

// 将 TodoList 组件渲染到页面
ReactDOM.render(<TodoList />, document.getElementById('root'));

上面就是我们一步步进行分析拆分来设计我们的应用程序的过程。由于例子比较简单,所以相对来说思路更加清晰,当我们面对大型的应用程序时,往往分析方式会有所变化,但是根本的原理是不变的。


参考资料

React 的编程思想
使用React并做一个简单的to-do-list

相关文章:

  • 【逻辑题】舅舅问题
  • 如何开发主控台部件
  • 优秀网站分享
  • 工具收藏
  • MongoDB 学习笔记之 WriteConcern
  • js new Date()的兼容性问题
  • Jedox Cloud安全可升级自助式商业情报
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)
  • 由插件封装引出的一丢丢思考
  • 背水一战 Windows 10 (53) - 控件(集合类): ItemsControl 的布局控件 - ItemsStackPanel, ItemsWrapGrid...
  • 三网卡服务器 配置三线三IP linux策略路由
  • Oracle 插入时间时 报错:ORA-01861: 文字与格式字符串不匹配 的解决办法
  • Entity Framework中的字符串插值引发担忧
  • CentOS 利用 yum 安装卸载软件常用命令
  • jarjar-maven-plugin打包
  • @jsonView过滤属性
  • android图片蒙层
  • bearychat的java client
  • Java,console输出实时的转向GUI textbox
  • JavaScript函数式编程(一)
  • npx命令介绍
  • ReactNative开发常用的三方模块
  • SpringBoot几种定时任务的实现方式
  • SpriteKit 技巧之添加背景图片
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • Tornado学习笔记(1)
  • 大型网站性能监测、分析与优化常见问题QA
  • 二维平面内的碰撞检测【一】
  • 记一次删除Git记录中的大文件的过程
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 那些被忽略的 JavaScript 数组方法细节
  • 思考 CSS 架构
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • ​​​​​​​​​​​​​​Γ函数
  • ​iOS实时查看App运行日志
  • ​用户画像从0到100的构建思路
  • #{}和${}的区别?
  • #每日一题合集#牛客JZ23-JZ33
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (四)c52学习之旅-流水LED灯
  • (原創) 物件導向與老子思想 (OO)
  • (转)jQuery 基础
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .cfg\.dat\.mak(持续补充)
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .NET Core 版本不支持的问题
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • @ComponentScan比较
  • [20161101]rman备份与数据文件变化7.txt