Create by jsliang on 2019-3-18 08:37:10
Recently revised in 2019-3-22 11:46:54
Hello 小夥伴們,如果覺得本文還不錯,記得給個 star , 小夥伴們的 star 是我持續更新的動力!GitHub 地址
一 目錄
不折騰的前端,和鹹魚有什麼區別
目錄 |
---|
一 目錄 |
二 前言 |
三 正文 |
3.1 新建 React 專案 |
3.2 專案目錄解析 |
3.3 精簡專案結構 |
3.4 初探元件 |
3.5 JSX |
3.6 事件及雙向資料繫結 |
3.7 優化-抽取 CSS |
3.8 優化-抽取 JS |
四 總結 |
五 參考文獻 |
二 前言
通過編寫一個簡單的 TodoList 小 Demo,熟悉 React 的開發流程。
三 正文
Now,開始搞事情。
3.1 新建 React 專案
- 下載 Node.js
- 安裝 React 腳手架:
npm i create-react-app -g
- 開啟新專案:
create-react-app todolist
cd todolist
npm start
- 開啟
localhost:3000
檢視頁面
3.2 專案目錄解析
- 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
、.gitignore
、README.md
是我們無需理會的,但是其他檔案,我們需要精簡下它們的程式碼:
- 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>
複製程式碼
- App.js
程式碼詳情
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="App">
Hello React!
</div>
);
}
}
export default App;
複製程式碼
- index.js
程式碼詳情
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
複製程式碼
- 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.jsTodoList.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 點需要講解下:
Fragment
是 React 提供的一種佔位符,它像<input>
、<span>
等標籤一樣,但是它在實際渲染的時候是不會出現的。因為 React 的 JSX 首層必須要有標籤,然後如果使用<div>
等會佔用一個層級,所以,類似於 Vue 的Template
,React 使用了Fragment
這種空標籤。constructor
表示父類的構造方法,在 ES6 中,構造方法constructor
相當於其建構函式,用來新建父類的this
物件,而super(props)
則是用來修正this
指向的。簡而言之,我們可以在這裡定義資料,並在整個 js 檔案中使用。onChange
這種寫法,是 React 指定的寫法,例如onClick
等,在原生 JS 中,使用的是onclick
,而在 React 中,為了區別,需要進行半駝峰編寫事件名字。同時,繫結的handleInputChange
,可以直接在render
下面進行編寫。
這樣,我們就對 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 個知識點:
- 在
render
中closeStyle
這個變數,我們用來定義 CSS 屬性,然後我們通過style={closeStyle}
,直接寫了個行內樣式(下面我們會抽離出來) - 關於 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
變了種寫法。
在這裡我們不用理會為什麼這麼寫,我們先接受這種寫法先。
- 關於改變
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 的改變:
- 我們在
constructor
中,將方法進行了提前定義:
this.handleInputChange = this.handleInputChange.bind(this);
複製程式碼
這樣,我們在下面就不用寫 .bind(this)
形式了。
- 我們修改了下
this.setState()
的形式:
原寫法:
this.setState({
list: list
})
複製程式碼
現寫法:
this.setState( () => ({
list: list
}))
複製程式碼
因為 React 16 版本進行了更新,使用這種寫法比之前的好,至於好在哪,我們先不關心,以後就用這種寫法了。
- 我們引用了元件:
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}
/>
)
})
}
複製程式碼
在這裡我們可以看到,我們通過自定義值的形式,將資料 key
、item
、index
傳遞給了子元件 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 有啥收穫:
- create-react-app 的安裝及開發。
- 元件化的思想及在 create-react-app 中關於元件化的應用。
- React 關於資料 data 以及方法 methods 的定義及使用,以及如何進行資料雙向繫結。
- 將大的元件拆分成小元件,並實現父子元件通訊(父元件傳遞引數給子元件,子元件呼叫父元件的方法)
至此,jsliang 就精通 jQuery、Vue、React 編寫 TodoList 了,哈哈!
五 參考文獻
- 《React.Component 與 React.PureComponent(React之效能優化)》
- 《visual studio code + react 開發環境搭建》
- 《react 中 constructor() 和 super() 到底是個啥?》
jsliang 廣告推送:
也許小夥伴想了解下雲伺服器
或者小夥伴想買一臺雲伺服器
或者小夥伴需要續費雲伺服器
歡迎點選 雲伺服器推廣 檢視!
jsliang 的文件庫 由 樑峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.com/LiangJunron…上的作品創作。
本許可協議授權之外的使用許可權可以從 creativecommons.org/licenses/by… 處獲得。