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… 處得到。

相關文章
相關標籤/搜索