React 系列一 之 TodoList

Create by jsliang on 2019-3-18 08:37:10
Recently revised in 2019-3-22 11:46:54php

Hello 小伙伴们,若是以为本文还不错,记得给个 star , 小伙伴们的 star 是我持续更新的动力!GitHub 地址css


【2019-08-16】Hello 小伙伴们,因为 jsliang 对文档库进行了重构,这篇文章中的一些连接可能失效,而 jsliang 缺少精力维护掘金这边的旧文章,对此深感抱歉。请须要获取最新文章的小伙伴,点击上面的 GitHub 地址,去文档库查看调整后的文章。html


一 目录

不折腾的前端,和咸鱼有什么区别前端

目录
一 目录
二 前言
三 正文
3.1 新建 React 项目
3.2 项目目录解析
3.3 精简项目结构
3.4 初探组件
3.5 JSX
3.6 事件及双向数据绑定
3.7 优化-抽取 CSS
3.8 优化-抽取 JS
四 总结
五 参考文献

二 前言

返回目录node

经过编写一个简单的 TodoList 小 Demo,熟悉 React 的开发流程。react

三 正文

返回目录git

Now,开始搞事情。github

3.1 新建 React 项目

返回目录shell

  1. 下载 Node.js
  2. 安装 React 脚手架:
    1. npm i create-react-app -g
  3. 开启新项目:
    1. create-react-app todolist
    2. cd todolist
    3. npm start
  4. 打开 localhost:3000 查看页面

3.2 项目目录解析

返回目录npm

- todolist
  + node_modules —————————— 项目依赖的第三方的包
  - public ———————————————— 共用文件
    - favicon.ico        —— 网页标签左上角小图标
    - index.html         —— 网站首页模板
    - mainfest.json      —— 提供 meta 信息给项目,并与 serviceWorker.js 相呼应,进行离线 APP 定义
  - src ——————————————————— 项目主要目录
    - App.css            —— 主组件样式
    - App.js             —— 主组件入口
    - App.test.js        —— 自动化测试文件
    - index.css          —— 全局 css 文件
    - index.js           —— 全部代码的入口
    - logo.svg           —— 页面的动态图
    - serviceWorker.js   —— PWA。帮助开发手机 APP 应用,具备缓存做用
  - .gitignore ——————————— 配置文件。git 上传的时候忽略哪些文件
  - package-lock.json ———— 锁定安装包的版本号,保证其余人在 npm i 的时候使用一致的 node 包
  - package.json ————————— node 包文件,介绍项目以及说明一些依赖包等
  - README.md ———————————— 项目介绍文件
复制代码

3.3 精简项目结构

返回目录

为了方便开发,下面对 creat-react-app 的初始目录进行精简:

- todolist
  + node_modules —————————— 项目依赖的第三方的包
  - public ———————————————— 共用文件
    - favicon.ico        —— 网页标签左上角小图标
    - index.html         —— 网站首页模板
  - src ——————————————————— 重要的目录
    - App.js             —— 主组件入口
    - index.js           —— 全部代码的入口
  - .gitignore ——————————— 配置文件。git 上传的时候忽略哪些文件
  - package.json ————————— node 包文件,介绍项目以及说明一些依赖包等
  - README.md ———————————— 项目介绍文件
复制代码

favicon.ico.gitignoreREADME.md 是咱们无需理会的,可是其余文件,咱们须要精简下它们的代码:

  1. index.html
代码详情
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>Todolist</title>
  </head>
  <body>
    <noscript>你须要容许在 APP 中运行 JavaScript</noscript>
    <div id="root"></div>
  </body>
</html>
复制代码
  1. App.js
代码详情
import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div className="App"> Hello React! </div>
    );
  }
}

export default App;
复制代码
  1. index.js
代码详情
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root')); 复制代码
  1. package.json
代码详情
{
  "name": "todolist",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.8.4",
    "react-dom": "^16.8.4",
    "react-scripts": "2.1.8"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}
复制代码

3.4 初探组件

返回目录

相似于上图,在进行页面开发的时候,咱们很容易地使用庖丁解牛的技巧,将页面进行划分,而后一部分一部分地将页面搭建出来。

给个比较官方的说法,就叫页面组件化:将页面切成几个部分,从而有利于页面的拼装以及代码的维护。

在 create-react-app 的默认配置中,App.js 就是一个组件,一块儿来看:

App.js

// 1. 引用 React 及其组件
import React, { Component } from 'react';

// 2. 定义一个叫 App 的组件继承于 Component
class App extends Component {
  render() {
    return (
      <div className="App"> Hello React! </div>
    );
  }
}

// 3. 根据 React 实例,在 App 内部编写完毕后,导出这个 App 组件
export default App;
复制代码

在上面,咱们引用、定义并导出了这个 App 的组件,而后咱们就要使用它:

index.js

// 1. 引入 React、ReactDOM
import React from 'react';
import ReactDOM from 'react-dom';

// 2. 将 App.js 导入进来
import App from './App';

// 3. 经过 ReactDOM 将 App.js 以虚拟 DOM 的形式渲染/挂载到 root 根节点,该节点在 index.html 中
ReactDOM.render(<App />, document.getElementById('root')); 复制代码

index.js 告诉咱们,它会经过 ReactDom,将 App.js 这个组件挂载到 root 这个节点上,那么,这个 root 在哪里呢?咱们查看下 index.html:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortc ut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>Todolist</title>
  </head>
  <body>
    <noscript>你须要容许在 APP 中运行 JavaScript</noscript>
    <div id="root"></div>
  </body>
</html>
复制代码

OK,很容易地咱们就捋清楚思路了:咱们在 index.html 中定义了个 root 根节点,而后咱们经过 index.js,将 App.js 以组件形式渲染到了 index.html 中,从而实现了节点的挂载。

思惟发散:咱们知道 index.js 和 App.js 的最终结合是挂载到 id="root" 节点上的,若是咱们再开一个 index2.js 和 App2.js,挂载到 id="root2" 节点上,行不行呢?亦或者咱们开一个 id="root3" 的节点,咱们在其中操做 jQuery,是否是也可行?

3.5 JSX

返回目录

在 create-react-app 的文件中,不论是 index.js 中的:

ReactDOM.render(<App />, document.getElementById('root')); 复制代码

仍是 App.js 中的:

class App extends React.Component {
  render() {
    return (
      <div className="App"> Hello React! </div>
    );
  }
}
复制代码

等这些有关 DOM 的渲染,都须要用到 JSX,所以须要引入 React:

import React from 'react';
复制代码
  • JSX 的定义

那么,什么是 JSX 呢?

React 的核心机制之一就是能够在内存中建立虚拟的 DOM 元素。React 利用虚拟 DOM 来减小对实际 DOM 的操做从而提高性能。

JSX 就是 JavaScript 和 XML 结合的一种格式。

React 发明了 JSX,利用 HTML 语法来建立虚拟 DOM。当遇到 <,JSX 就当 HTML 解析,遇到 { 就当 JavaScript 解析。

  • JSX 的使用

在 JSX 语法中,若是咱们须要使用本身建立的组件,咱们直接使用它的定义名便可,例如:

index.js

// 1. 引入 React、ReactDOM
import React from 'react';
import ReactDOM from 'react-dom';

// 2. 将 App.js 导入进来
import App from './App';

// 3. 经过 ReactDOM 将 App.js 以虚拟 DOM 的形式渲染/挂载到 root 根节点,该节点在 index.html 中
ReactDOM.render(<App />, document.getElementById('root')); 复制代码

其中第三点便是自定义组件渲染到根节点。

提示:在 React 中,若是须要使用自定义组件,那么该组件不能小写开头 app,而是使用 App 这样的大写开头形式。

3.6 事件及双向数据绑定

返回目录

这是咱们精简后的目录结构:

咱们修改下目录结构,开始编写 TodoList:

首先,咱们修改 App.js 为 TodoList.js:

App.js TodoList.js

import React, { Component } from 'react';

class TodoList extends Component {
  render() {
    return (
      <div className="TodoList"> Hello React! </div>
    );
  }
}

export default TodoList;
复制代码

而后,咱们修改 index.js 中挂载到 index.html 的组件为 TodoList:

index.js

import React from 'react';
import ReactDOM from 'react-dom';

import TodoList from './TodoList';

// 3. 经过 ReactDOM 将 App.js 以虚拟 DOM 的形式渲染/挂载到 root 根节点,该节点在 index.html 中
ReactDOM.render(<TodoList />, document.getElementById('root')); 复制代码

修改完毕,小伙伴们能够重启下 3000 端口,查看下咱们的 React 是否能正常启动。

在此步骤中,咱们仅仅修改 App.js 为 TodoList.js,使 index.js 挂载到 root 的是 TodoList.js,除此以外没进行其余操做。

最后,若是没有问题,那么咱们进一步编写 TodoList,获取到 input 输入框的值,并渲染到列表中:

TodoList.js

代码详情
// Fragment 是一种占位符形式,相似于 Vue 的 Template
import React, { Component, Fragment } from 'react';

class TodoList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      inputValue: '',
      list: []
    }
  }

  render() {
    return (
      <Fragment> <div> {/* 单项数据绑定 */} <input type="text" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} /> <button>提交</button> </div> <ul> <li> <span>吃饭</span> <span>X</span> </li> <li> <span>睡觉</span> <span>X</span> </li> <li> <span>打豆豆</span> <span>X</span> </li> </ul> </Fragment> ) } handleInputChange(e) { console.log(e.target.value); this.setState({ inputValue: e.target.value }) } } export default TodoList; 复制代码

咱们先查看演示:

OK,这样咱们在每输入一个字符的时候,咱们就能马上获取到对应的数据,这时候实现了单向数据流:输入框 -> JS 内存。

其中有 3 点须要讲解下:

  1. Fragment 是 React 提供的一种占位符,它像 <input><span> 等标签同样,可是它在实际渲染的时候是不会出现的。由于 React 的 JSX 首层必需要有标签,而后若是使用 <div> 等会占用一个层级,因此,相似于 Vue 的 Template,React 使用了 Fragment 这种空标签。
  2. constructor 表示父类的构造方法,在 ES6 中,构造方法 constructor 至关于其构造函数,用来新建父类的 this 对象,而 super(props) 则是用来修正 this 指向的。简而言之,咱们能够在这里定义数据,并在整个 js 文件中使用。
  3. onChange 这种写法,是 React 指定的写法,例如 onClick 等,在原生 JS 中,使用的是 onclick,而在 React 中,为了区别,须要进行半驼峰编写事件名字。同时,绑定的 handleInputChange,能够直接在 render 下面进行编写。

参考文献:《react 中 constructor() 和 super() 究竟是个啥?》

这样,咱们就对 React 的数据及事件有了初步理解,下面咱们加下按钮点击新增列表事件以及点击 X 删除列表事件。

TodoList.js

代码详情
// Fragment 是一种占位符形式,相似于 Vue 的 Template
import React, { Component, Fragment } from 'react';

class TodoList extends Component {

  // 构造函数
  constructor(props) {
    super(props);
    // 定义数据
    this.state = {
      inputValue: '',
      list: []
    }
  }

  // 渲染页面
  render() {
    let closeStyle = {
      fontSize: '1.2em',
      color: 'deepskyblue',
      marginLeft: '10px'
    }
    return (
      <Fragment> <div> {/* 单项数据绑定 */} {/* 在 React 中,绑定时间的,通常为半驼峰形式 */} <input type="text" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} /> <button onClick={this.handleBtnClick.bind(this)}>提交</button> </div> <ul> { this.state.list.map( (item, index) => { return <li key={index}> <span>{index}. {item}</span> <span style={closeStyle} onClick={this.handleItemDelete.bind(this, index)}>X</span> </li> }) } </ul> </Fragment> ) } // 方法体 - 输入内容 handleInputChange(e) { this.setState({ inputValue: e.target.value }) } // 方法体 - 点击提交 handleBtnClick() { this.setState({ list: [...this.state.list, this.state.inputValue], inputValue: '' }) } // 方法体 - 删除项目 handleItemDelete(index) { const list = [...this.state.list]; list.splice(index, 1); this.setState({ list: list }) } } export default TodoList; 复制代码

在这一部分,咱们须要了解 3 个知识点:

  1. rendercloseStyle 这个变量,咱们用来定义 CSS 属性,而后咱们经过 style={closeStyle},直接写了个行内样式(下面咱们会抽离出来)
  2. 关于 JSX 遍历输出的形式:
{
  this.state.list.map( (item, index) => {
    return <li key={index}> <span>{index}. {item}</span> <span style={closeStyle} onClick={this.handleItemDelete.bind(this, index)}>X</span> </li>
  })
}
复制代码

咱们经过 {} 里面循环输出 DOM 节点。若是你学过 jQuery,那么能够将它当为拼接字符串;若是你学过 Vue,那么能够将它当成 v-for 变了种写法。

在这里咱们不用理会为何这么写,咱们先接受这种写法先。

  1. 关于改变 constructor 中的数据,咱们使用:
this.setState({
  list: list
})
复制代码

这种形式。其实,这也是有记忆技巧的,要知道咱们在定义数据的时候,使用了:

// 定义数据
this.state = {
  inputValue: '',
  list: []
}
复制代码

即: this.state,那么咱们须要修改数据,那就是 this.setState 了。

至此,咱们的简易 TodoList 就实现了,下面咱们进一步优化,将 CSS 和 JS 进一步抽取。

3.7 优化-抽取 CSS

返回目录

在上面中,咱们提到 closeStyle 是一种行内的写法,做为一枚 完美编程者,咱们确定不能容忍,下面咱们开始抽离:

TodoList.js

import React, { Component, Fragment } from 'react';

import './style.css'

// ... 省略中间代码

<ul>
{
  this.state.list.map( (item, index) => {
    return <li key={index}>
      <span>{index}. {item}</span>
      <span className="icon-close" onClick={this.handleItemDelete.bind(this, index)}>X</span>
    </li>
  })
}
</ul>
复制代码

在这里,咱们须要知道:咱们能够经过 import 的形式,直接将 CSS 文件直接导入,而后,咱们命名 class 的时候,由于 React 怕 JS 的 class 与 HTML 的 class 冲突,因此咱们须要使用 className

最后咱们再编写下 CSS 文件:

.icon-close {
  font-size: 1.2em;
  color: deepskyblue;
  margin-left: 10px;
}
复制代码

如此,咱们就实现了 CSS 的抽取。

3.8 优化-抽取 JS

返回目录

在第 4 章关于组件的介绍中,咱们讲到:一些复杂的 JS 是能够抽取出来,并以组件的形式,嵌入到须要放置的位置的。

那么,咱们在 JSX 越写越多的状况下,是否是能够将列表渲染那部分抽取出来,从而精简下 JSX 呢?

答案是能够的,下面咱们看下实现:

TodoList.js

代码详情
// Fragment 是一种占位符形式,相似于 Vue 的 Template
import React, { Component, Fragment } from 'react';

// 引入组件
import TodoItem from './TodoItem';

// 引用样式
import './style.css';

class TodoList extends Component {

  // 构造函数
  constructor(props) {
    super(props);
    // 定义数据
    this.state = {
      inputValue: '',
      list: []
    }
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
  }

  // 渲染页面
  render() {
    return (
      <Fragment>
        <div>
          <label htmlFor="insertArea">输入内容:</label>
          {/* 单项数据绑定 */}
          {/* 在 React 中,绑定时间的,通常为半驼峰形式 */}
          <input 
            id="insertArea"
            type="text" 
            value={this.state.inputValue}
            onChange={this.handleInputChange}
          />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul>
          {/* 精简 JSX,将部分抽取出来 */}
          { this.getTodoItem() }
        </ul>
      </Fragment>
    )
  }

  // 获取单独项
  getTodoItem() {
    return this.state.list.map( (item, index) => {
      return (
        <TodoItem 
          key={index}
          item={item} 
          index={index}
          handleItemDelete={this.handleItemDelete}
        />
      )
    })
  }

  // 方法体 - 输入内容
  handleInputChange(e) {
    const value = e.target.value;
    this.setState( () => ({
      inputValue: value
    }))
  }

  // 方法体 - 点击提交
  handleBtnClick() {
    const list = this.state.list,
          inputValue = this.state.inputValue;
    this.setState( () => ({
      list: [...list, inputValue],
      inputValue: ''
    }))
  }

  // 方法体 - 删除项目
  handleItemDelete(index) {
    // immutable - state 不容许作任何改变
    const list = [...this.state.list];
    list.splice(index, 1);

    this.setState( () => ({
      list: list
    }))
  }

}

export default TodoList;
复制代码

咱们关注下 TodoList.js 的改变:

  1. 咱们在 constructor 中,将方法进行了提早定义:
this.handleInputChange = this.handleInputChange.bind(this);
复制代码

这样,咱们在下面就不用写 .bind(this) 形式了。

  1. 咱们修改了下 this.setState() 的形式:

原写法:

this.setState({
  list: list
})
复制代码

现写法:

this.setState( () => ({
  list: list
}))
复制代码

由于 React 16 版本进行了更新,使用这种写法比以前的好,至于好在哪,咱先不关心,之后就用这种写法了。

  1. 咱们引用了组件:
import TodoItem from './TodoItem';
复制代码

而且将组件放到方法体:this.getTodoItem() 中,而 this.getTodoItem() 的定义是:

// 获取单独项
getTodoItem() {
 return this.state.list.map( (item, index) => {
   return (
     <TodoItem key={index} item={item} index={index} handleItemDelete={this.handleItemDelete} /> ) }) } 复制代码

在这里咱们能够看到,咱们经过自定义值的形式,将数据 keyitemindex 传递给了子组件 TodoItem。同时,经过 handleItemDelete,将本身的方法传递给了子组件,这样子组件就能够调用父组件的方法了:

TodoItem.js

代码详情
import React, { Component } from 'react'

class TodoItem extends Component {

  constructor(props) {
    super(props);
    // 这种写法能够节省性能
    this.handleClick = this.handleClick.bind(this);
  }

  render() {
    const { item } = this.props;
    return (
      <li> <span>{item}</span> <span className="icon-close" onClick={this.handleClick}>X</span> </li>
    )
  }

  handleClick() {
    const { handleItemDelete, index } = this.props;
    handleItemDelete(index);
  }

}

export default TodoItem;
复制代码

这样,咱们就完成了组件的抽取,并学会了

  • 父组件传递值给子组件
  • 子组件调用父组件的方法

由此,咱们在接下来就能够编写更丰富健全的项目了。

本文代码地址:React 系列源码地址

四 总结

返回目录

在咱们学习任意一门语言中,大多就是上手 “Hello World!” 编程~

而后作小案例的时候,咱们都喜欢来个 TodoList,由于它能讲清楚一些有关基础的知识点。

如今,咱们回顾下,咱们开发 React 的 TodoList 有啥收获:

  1. create-react-app 的安装及开发。
  2. 组件化的思想及在 create-react-app 中关于组件化的应用。
  3. React 关于数据 data 以及方法 methods 的定义及使用,以及如何进行数据双向绑定。
  4. 将大的组件拆分红小组件,并实现父子组件通信(父组件传递参数给子组件,子组件调用父组件的方法)

至此,jsliang 就精通 jQuery、Vue、React 编写 TodoList 了,哈哈!

五 参考文献

返回目录

  1. 《React.Component 与 React.PureComponent(React之性能优化)》
  2. 《visual studio code + react 开发环境搭建》
  3. 《react 中 constructor() 和 super() 究竟是个啥?》

jsliang 广告推送:
也许小伙伴想了解下云服务器
或者小伙伴想买一台云服务器
或者小伙伴须要续费云服务器
欢迎点击 云服务器推广 查看!

知识共享许可协议
jsliang 的文档库梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/LiangJunron…上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。

相关文章
相关标签/搜索