React 是一個用於構建使用者介面的 JavaScript 庫,主要特點有:
- 宣告式渲染:設計好資料和檢視的關係,資料變化 React 自動渲染,不必親自操作DOM
- 元件化:頁面切分成多個小部件,通過組裝拼成整體頁面,利於程式碼複用
本文通過寫個簡單的 TodoList
例項,不求甚解,熟悉下 React 的開發過程。
1. 安裝 Node.js
Node.js
是一個執行環境,類似 jdk
,用以支援在服務端執行 JavaScript。
您可以在這裡下載安裝包:
http://nodejs.cn/download/
以綠色版安裝為例,將 node-v10.16.1-win-x64.zip 解壓到 E:\software\ 並命名為 node-v10.16.1
在 Path 環境變數中增加兩項:
E:\software\node-v10.16.1\
E:\software\node-v10.16.1\node_global
在 cmd 中使用 node -v
顯示版本號,表示安裝成功。
Node.js 中有個 npm
軟體包管理器,可以很方便的管理下載和使用第三方開源包,類似 maven
,使用 npm -v
顯示版本號,表示 npm 也沒有問題。
綠色版安裝完成後一些必要的配置:
npm config set prefix "E:\software\node-v10.16.1\node_global"
設定全域性安裝的模組儲存路徑
npm config set cache "E:\software\node-v10.16.1\node_cache"
設定下載快取的儲存路徑
npm config set registry https://registry.npm.taobao.org`
設定 npm 下載源為淘寶映象
簡單使用:
- npm install xxx: 安裝到專案目錄
- npm install -g xxx 安裝到全域性目錄
- npm install -save xxx: 安裝到專案目錄,並在 package.json 中的 dependencies 節點記錄依賴
- npm install --save-dev xxx: 安裝到專案目錄,並在 package.json 中的 devDependencies 節點記錄依賴
2. 腳手架建立專案
React 官方出的腳手架工具 create-react-app
,可以一鍵建立一個 Web 應用程式:
cmd> npm install -g create-react-app
cmd> cd E:
cmd> create-react-app react-todoapp
cmd> cd react-todoapp
腳手架會在當前目錄建立一個 react-todoapp
目錄:
react-todoapp
├── README.md
├── node_modules
├── package.json
├── package-lock.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
└── setupTests.js
目錄中主要的檔案和資料夾說明:
- README.md: 專案簡介,支援 Markdown 語法
- node_modules: 專案的依賴包,類似
Maven Repository
- package.json: 配置專案依賴的第三方包,類似
pom.xml
- package-lock.json: 鎖定第三方包的版本號,保證
npm install
版本一致 - public: 公開資源,網站路徑,類似 nginx 的 html 目錄
- src: 核心元件程式碼檔案
為了便於開發,刪除目錄中不必要的檔案,最終結構如下:
react-todoapp
├── README.md
├── node_modules
├── package.json
├── package-lock.json
├── .gitignore
├── public
│ └── index.html
└── src
├── App.css
├── index.js
├── TodoApp.js
├── TodoItem.js
└── TodoList.js
接下來,設計與實現一個 TodoList 的例子,我們把所有程式碼過一下,敲一遍,先不管為什麼,跑起來,最後再整理下知識點。
3. 例項 TodoApp
主要實現功能有:
- 新增一個待辦事項
- 刪除一個待辦事項
- 勾選核取方塊標記事項已完成
如圖所示,總共將頁面拆分成了三個元件:TodoApp
, TodoList
和 TodoItem
。
3.1 index.js 入口檔案
應該可以類比 java 的 main
方法,在 src 目錄新建 index.js
內容如下:
// 引入 React, ReactDOM
import React from 'react';
import ReactDOM from 'react-dom';
// 引入 TodoApp 元件
import TodoApp from './TodoApp';
// 將渲染結果掛在到 root 節點,該節點在 index.html 中
ReactDOM.render(
<React.StrictMode>
<TodoApp />
</React.StrictMode>,
document.getElementById('root')
);
先匯入需要使用的元件(類),然後呼叫它們提供的方法和服務,有沒有些許眼熟?
3.2 TodoApp.js
TodoApp 設計了頁面整體佈局,它包含全部資料以及操作這些資料的方法,是其他兩個元件的父元件
:
import React, { Component } from 'react';
import TodoList from './TodoList';
import './app.css';
class TodoApp extends Component {
constructor(props) { // 構造方法,props 應該是父類的一個成員變數
super(props);
this.state = { // 元件狀態資料
text: '',
items:[{id: 1, status: 1, text: "去月球"},{id: 2, status: 0, text: "去火星"}]
};
// 設定 this 指向,預設 undefined
this.handleChange = this.handleChange.bind(this);
this.handleAdd = this.handleAdd.bind(this);
this.handleComplete = this.handleComplete.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
// 渲染解析 jsx
render() {
return (
<div className="todo">
<h3 className="text-center">Todos App</h3>
<TodoList
items={this.state.items}
handleComplete={this.handleComplete}
handleDelete={this.handleDelete} />
<input className="input" type="text" placeholder="新增新任務"
value={this.state.text}
onChange={this.handleChange} />
<button className="btn-add" onClick={this.handleAdd}>新增</button>
</div>
);
}
handleChange(e) {
this.setState({ text: e.target.value })
}
handleAdd(e) {
e.preventDefault();
if (this.state.text.length === 0) {
return;
}
const newItem = {
id: Date.now(),
text: this.state.text,
status: 0
};
this.setState({
items: [...this.state.items, newItem],
text: ''
});
}
handleComplete(taskid) {
// 臨時變數,不直接修改原資料
let items = this.state.items;
let findItem = items.find(item => item.id === taskid);
findItem.status = findItem.status === 0 ? 1 : 0;
this.setState({
items: items
});
}
handleDelete(taskid) {
let items = this.state.items;
items = items.filter(item => item.id !== taskid);
this.setState({
items: items
});
}
}
export default TodoApp;
3.3 TodoList.js
TodoList 接收父元件 TodoApp 中的陣列,並將其渲染成一個 ul
列表:
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class TodoList extends Component {
render() {
return (
<ul className="list">
{
this.props.items.map((item)=>{
return (
<TodoItem key={item.id}
taskid={item.id}
status = {item.status}
text={item.text}
handleComplete={this.props.handleComplete}
handleDelete={this.props.handleDelete} />
)
})
}
</ul>
);
}
}
export default TodoList;
3.4 TodoItem.js
在 TodoList 遍歷陣列時,把每一項元素交給 TodoItem 元件,它會渲染成一個 li
元素:
import React, { Component } from 'react';
class TodoItem extends Component {
constructor(props) {
super(props);
this.taskComplete = this.taskComplete.bind(this);
this.taskDelete = this.taskDelete.bind(this);
}
render() {
let isCompleted = this.props.status === 1;
return (
<li className={isCompleted?'complete':''}>
<input type="checkbox"
checked={isCompleted}
onChange={this.taskComplete}/>
<span>{this.props.text}</span>
<button className="btn-del" onClick={this.taskDelete}>刪除</button>
</li>
);
}
taskComplete() {
this.props.handleComplete(this.props.taskid);
}
taskDelete() {
this.props.handleDelete(this.props.taskid);
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text !== this.props.text || nextProps.status !== this.props.status) {
return true;
} else {
return false;
}
}
}
export default TodoItem;
這幾個檔案寫完之後,進入 react-todoapp
目錄,cmd 執行 npm start
,訪問 http://localhost:3000 就能檢視最終的結果了。
4. 思考
4.1 JSX
TodoApp 元件在 render 方法渲染時,使用了一個既不是字串也不是 HTML 的語法,它被稱為 JSX
,是 JavaScript 的語法擴充套件,使用它可以很方便的建立 DOM。
JSX 看起來像是模板語言,但它具有 JavaScript 的全部功能:
- 遇到
<>
就當作 HTML 解析 - 遇到
{}
就當作 JavaScript 解析
4.2 元件通訊
這裡主要有兩種通訊情況:
- 父元件向子元件通訊
- 子元件向父元件通訊
每個元件都有一個 props
物件,用以訪問元件的屬性,所以,父元件可以向子元件傳遞一組 props 供其使用,就像方法傳參一樣。
子元件向父元件通訊,可以利用回撥函式:父元件將一個函式作為 props 傳遞給子元件,子元件呼叫該回撥函式,便可向父元件通訊。
回撥函式也是增刪一組資料,那麼為什麼不直接把資料傳給子元件,直接操作?這是因為 props
是只讀的,不能修改,改了就會報錯:
TypeError: Cannot assign to read only property 'items' of object '#<Object>'
這樣設計是為了保證相同的輸入,每次都輸出相同的結果。
4.3 元件狀態
元件分有狀態和無狀態,比如 TodoApp 是有狀態的,TodoList 和 TodoItem 是無狀態的。這個狀態 state
和 props
類似,也是一組資料,但它是元件內部私有的,其他元件訪問不了。
所以,TodoApp 元件只有在自己內部才可以對 this.state.items
內部增刪改,就算把它傳給其他元件,也是隻讀的。
在更新 state 時需要注意:
- 不能直接修改 state:統一使用
setState()
更新 setState
是一個非同步方法,可傳遞一個函式在執行結束後回撥,setState({},()=>{..})
setState
會合並更新,就是可以只傳遞變更的部分
元件的 state 可以隨著使用者互動而產生變化,但 props 一旦定義就不再發生改變。
4.4 單向資料流
子元件不能直接修改父元件的資料,資料只能由父元件傳給子元件,更新只能通過回撥,這個特性被稱作單向資料流
。
它保證了元件相同的輸入,每次都是相同的輸出。所有資料都在父元件,程式碼易於理解,方便維護。
4.5 其他
import React, { Component } from 'react';
這是 ES6 解構賦值
的用法,解構賦值是對賦值運算子的擴充套件,可以將屬性/值從物件/陣列中取出,賦值給其他變數。
這個匯入就相當於:
import React from 'react';
const Component = React.Component;
類比 Java 的話,可以這樣理解,React 相當於包,Component 相當於包下的類,要使用都要先匯入。
5. 總結
簡單寫了下自己的理解,僅供參考!還是要看官方文件:
- 中文:https://react.docschina.org/docs/getting-started.html
- 英文:https://reactjs.org/docs/getting-started.html
此次編寫的 react-todoapp 原始碼地址:https://github.com/chuondev/react-todoapp