前面的話
這是一篇算React入門級的文章,初次接觸React,有問題請指出~~~。
安裝方式
這裏介紹搭建一個 react 項目的兩種方式: CDN鏈接 和 官方推薦的 create-react-app 腳手架
CDN 鏈接
創建一個 index.html 文件,將 React、React DOM、Babel 這三個 CDN 鏈接引入到 head 標籤中。並創建一個 div ,id爲 root,作爲根元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello React</title>
<!-- React --- React頂級API -->
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
<!-- React DOM --- 添加特定與DOM的方法 -->
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<!-- Babel ---js編譯器,可使用es6+ -->
<script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<!-- text/babel 腳本類型是babel所必須的 -->
<script type="text/babel">
// react code
</script>
</body>
</html>
引入這三個 CDN 鏈接的目的:
- React – 頂級API
- React DOM —添加特定DOM的方法
- Babel — 種JavaScript編譯器, 可使用ES6+
如果需要安裝指定的版本,將@後面的版本號改爲指定版本。
⚠️注意:script 腳本的類型 text/babel
是必須的。
我們都知道,不管是 Vue 還是 React 都是組件化,在 React 中有兩種創建組件的方法,Class
組件和 Function
組件, 這裏我們創建一個 Class 組件。
創建一個 Class 組件很簡單,只要創建一個 Class
類,並讓他繼承 React.Component
, 在render()
方法中 return
一個 JSX
模版,用於呈現 DOM節點。
⚠️注意:render() 方法是類組件唯一需要的方法。
創建組件App :
// 創建組件App
class App extends React.Component {
// render方法必須,用於呈現DOM節點
render() {
return (
// jsx
<h1>hello word</h1>
)
}
}
在 return
中, 可以看到返回很像 HTML 的內容,但其不是 HTML ,而是 JSX ,這裏先不過多介紹。
最後,我們使用 ReactDOM.render()
方法將 App組件渲染到 root
根元素 div 中:
ReactDOM.render(<App/>, document.getElementById('root'));
index.html 的完整代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello React</title>
<!-- React --- React頂級API -->
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
<!-- React DOM --- 添加特定與DOM的方法 -->
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<!-- Babel ---js編譯器,可使用es6+ -->
<script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<!-- text/babel 腳本類型是babel所必須的 -->
<script type="text/babel">
// 創建組件App
class App extends React.Component {
// render方法必須,用於呈現DOM節點
render() {
return (
// jsx
<h1>hello word</h1>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
</script>
</body>
</html>
在瀏覽器中打開 index.html 文件:
這樣一個簡單的 React 應用就搭建好了。
create-react-app
第一種搭建 react 項目的方法只適合書寫簡單的react 代碼,使用 create-react-app
腳手架可以幫我們初始化一個項目 demo。
npx create-react-app react-start
項目結構:public/index.html 是頁面模板,src/index.js 則是入口JS文件
安裝完成後,進入項目文件並啓動:
cd react-start
npm start
運行後,會彈出新窗口:
下面修改一下代碼,修改App.js 文件爲上面創建 App 組件的代碼:
import React, { Component } from 'react';
// import './App.css';
class App extends Component {
render() {
return <h1>Hello, React</h1>;
}
}
export default App;
什麼是JSX?
上面的代碼中已經接觸了 JSX,看起來很像 HTML ,但是並不是。它是JSX,代表 JavaScript XML 。
接着上面的代碼,修改App.js :
const name = 'React';
const element = (
<div tabIndex="0" className="box">
<h1>Hello, {name}</h1>
<div className="name">
<span>xiaoqi</span>
</div>
</div>
);
class App extends Component {
render() {
return element;
}
}
element 就是一個 JSX,可以看到它的特點:
- 可以使用 { } 內置 js 表達式
- JSX 標籤可以包含很多子標籤
- JSX 中可以指定特定的屬性(採用大駝峯),比如上面的
tabIndex
、className
看一下渲染之後的效果:
元素渲染
元素是構成 React 應用的最小磚塊。
創建一個元素 element :
const element = (
<div className="box">
<h1>React 入門</h1>
<span> 元素是構成 React 應用的最小磚塊。</span>
</div>
);
將這個元素渲染到 DOM 根結點中,需要使用 ReactDOM.render()
方法:
ReactDOM.render(element, document.getElementById('root'));
因 index.js 文件是項目的 js 入口文件,所以我們修改 index.js 文件爲上面的代碼:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
// import * as serviceWorker from './serviceWorker';
const element = (
<div className="box">
<h1>React 入門</h1>
<span> 元素是構成 React 應用的最小磚塊。</span>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
運行結果:
組件
組件分爲兩種: Class 組件和 Function 組件。
大多數的 React 應用都由很多小組件組成,將所有的小組件都添加到App主組件中,最後將 App主組件渲染到入口 js 文件 index.js 中。
我們將修改 App.js
和 index.js
文件:
App.js:
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="app">
<h1> Hello, React</h1>
</div>
);
}
}
export default App;
index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
Class 組件
前面提過,創建一個 Class 組件,只需要創建一個 class 類,並且讓它繼承React.Component
, 在 render()
方法中 return
出 JSX
模版。
接下來,我們在 src 文件夾中 ,創建一個 Table.js文件:
// Table組件
import React, { Component } from 'react';
class Table extends Component {
render() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
<tbody>
<tr>
<td>Charlie</td>
<td>Janitor</td>
</tr>
<tr>
<td>Mac</td>
<td>Bouncer</td>
</tr>
<tr>
<td>Dee</td>
<td>Aspiring actress</td>
</tr>
</table>
);
}
}
export default Table;
在 App.js中添加 Table組件:
class App extends Component {
render() {
return (
<div className="app">
<h1> Hello, React</h1>
<Table />
</div>
);
}
}
運行結果:
Function 組件
上面的 Table 組件, 我們可以拆分爲兩個子組件:TableBody
組件和TableHeader
組件, 這兩個組件我們使用 Function 組件來創建:
修改 Table.js : TableHeader
和 TableBody
組件都在Table.js文件中,並被 Table
組件使用。
// TableHeader組件
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
);
};
// TableBody 組件
const TableBody = () => {
return (
<tbody>
<tr>
<td>Charlie</td>
<td>Janitor</td>
</tr>
<tr>
<td>Mac</td>
<td>Bouncer</td>
</tr>
<tr>
<td>Dee</td>
<td>Aspiring actress</td>
</tr>
</tbody>
);
};
class Table extends Component {
render() {
return (
<table>
<TableHeader />
<TableBody />
</table>
);
}
}
小結
比較一下 Function
組件與Class
組件:
Function 組件:
const functionComponent = () => {
return <div> Example</div>
}
Class 組件:
class ClassComponent entends React.Component {
render() {
return <div> Example</div>
}
}
組件通信: Props
Vue中的父傳子使用的是 props,React 中的父傳子也是props,下面看一下在React中props是如何使用的。
如果表格的數據很多,那麼上面的TableBody
組件會顯的很臃腫。我們將< tbody>中要輸出的數據提取出來,通過props的方式來進行傳遞。
修改App.js文件:
-
在App組件中聲明數據
characters
, 表示< tbody>中要輸出的數據。 -
在子組件
Table
上添加屬性名稱和數據class App extends Component { render() { // 提取數據 const characters = [ { name: 'Charlie', job: 'Janitor', }, { name: 'Mac', job: 'Bouncer', }, { name: 'Dee', job: 'Aspring actress', }, ]; return ( <div className="app"> <h1> Hello, React</h1> <Table characterData={characters} /> </div> ); } }
修改 Table.js :
-
子組件中的使用:在 class 組件中通過
this.props.屬性名
來獲取父組件中傳遞的數據:class Table extends Component { render() { const { characterData } = this.props; return ( <table> <TableHeader /> <TableBody characterData={characterData} /> </table> ); } }
這裏的
const { characterData } = this.props
相當於const characterData = this.props.characterData
,這裏只是es6的寫法。 -
繼續將數據傳遞給
Table
組件的子組件TableBody
:TableBody
是一個 function組件,其使用 props 的方法直接是:將props作爲參數名傳遞給function組件,使用props.屬性名
即可獲取父組件的數據。// TableBody 組件 const TableBody = (props) => { const rows = props.characterData.map((item, index) => { return ( <tr key={index}> <td>{item.name}</td> <td>{item.job}</td> </tr> ); }); return <tbody>{rows}</tbody>; };
上面的例子中,給每個錶行都添加了一個
key
。React 中創建列表時,使用都要使用key
,來識別列表的每一項項,這是必要的。
⚠️注意:props
具有隻讀性,無論是函數聲明還是class聲明的組件,都不能修改自身的props
State狀態
props 是單向傳遞,並且是隻讀的。如果想改變數據的狀態,可以使用state,與props 不同的是state可變,但是 state 是class組件私有的對象,修改state 不能直接修改,要使用this.setState()
方法來改變。
現在我們實現一個功能:在表的每行添加一個刪除按鈕,點擊之後,可刪除該行信息。
修改 App.js
- 創建一個
state
對象,包含存儲的數據 characters - 由於要刪除數據,即要改變 state 對象,我們添加一個
removeCharacter()
方法,在該方法內實現 state 數據的變化,通過數組的索引過濾,然後返回新的數組。 - 最後將該函數傳遞給子組件
Table
class App extends Component {
state = {
characters: [
{
name: 'Charlie',
job: 'Janitor',
},
{
name: 'Mac',
job: 'Bouncer',
},
{
name: 'Dee',
job: 'Aspring actress',
},
],
};
// 箭頭函數解決 class中this不綁定的問題
removeCharacter = (i) => {
this.setState({
characters: this.state.characters.filter((item, index) => {
return i !== index;
}),
});
};
render() {
// 提取數據
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
</div>
);
}
}
將函數removeCharactor
傳遞給Table
組件之後,還需要傳遞給TableBody
組件,並且還要在 TableBody
組件中給每行添加一個按鈕,點擊之後執行此函數。
修改 Table.js:
// Table組件
import React, { Component } from 'react';
// TableHeader組件
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
);
};
// TableBody 組件
const TableBody = (props) => {
const rows = props.characterData.map((item, index) => {
return (
<tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={() => props.removeCharacter(index)}>Dalete</button>
</td>
</tr>
);
});
return <tbody>{rows}</tbody>;
};
class Table extends Component {
render() {
const { characterData, removeCharacter } = this.props;
return (
<table>
<TableHeader />
<TableBody
characterData={characterData}
removeCharacter={removeCharacter}
/>
</table>
);
}
}
該onClick函數必須通過返回該removeCharacter()方法的函數,否則它將嘗試自動運行。
運行結果:
⚠️注意: State 的更新可能是異步的, React 可能會把多個setState() 調用合併成一個調用。
props 與 state的區別
- state 是管理數據,控制狀態,可變(通過this.setState() )
- props 是外部傳入的數據參數,不可變
- 沒有 state 的叫無狀態組件,有 state 的叫有狀態組件
- 多用 props,少用 state。
事件處理
React 中事件處理和 DOM 元素的很相似,但是在語法上有些不同:
- React 事件的命名採用小駝峯(camelCase)
- 使用 JSX 語法時需要傳入一個函數作爲事件處理函數
例子:
<button onClick={activateLasers}>
Activate Lasers
</button>
前面,我們已經實現了將數據存儲在state狀態中,並且可以從狀態中刪除任何數據。但如果我們想添加新的數據怎麼辦呢?
接下來,通過下面的表單例子,來體驗 React 中的事件處理。我們添加一個 Form.js 組件, 每次更改表單中字段時會更新狀態,並且在我們提交時,所有數據會傳遞給 App組件的 state,進而更新整個 Table。
創建新文件 Form.js :
-
我們將表單的初始狀態設置爲帶有空屬性的對像,並且將初始狀態賦給
this.state
:import React, { Component } from 'react' class Form extends Component { initialState = { name: '', job: '', } state = this.initialState }
-
表單內容大致如下:
render() { const { name, job } = this.state; // 初始化時爲空 return ( <form> <label htmlFor="name">name</label> <input type="text" name="name" id="name" value={name} onChange={this.handleChange} /> <label htmlFor="job">job</label> <input type="text" name="job" id="job" value={job} onChange={this.handleChange} /> </form> ); }
-
每次輸入時都更改
state
裏面的數據,所以在onChange
事件發生時,將handleChangr()
方法作爲回調傳入handleChange = (event) => { const { name, value } = event.target; this.setState({ [name]: value, }); };
⚠️注意:
在class 組件中,class 的方法默認不會綁定 this
,所以要謹慎 JSX 回調函數中的 this 。例如上面的例子中,如果忘記綁定 this
,直接將this.handleChange
傳入 onChange
,這時調用這個函數時, this
將會是是undefined
。
綁定this的方法
-
第一種:class 的方法使用箭頭函數, 如上例。
handleChange = (event) => { const { name, value } = event.target; this.setState({ [name]: value, }); };
-
第二種: 在構造函數中使用
this.handleChange.bind(this)
進行綁定。constructor() { super(); this.handleChange = this.handleChange.bind(this); } handleChange(event) { const { name, value } = event.target; this.setState({ [name]: value, }); }
-
第三種:其實也相當於第二種,只不過不使用構造函數
handleChange(event) { const { name, value } = event.target; this.setState({ [name]: value, }); // 直接在傳入時進行綁定 onChange={this.handleChange.bind(this)}
前面實現了表單的數據更新,最後一步是提交表單的數據並更新App組件的 state
狀態。
修改App.js:
創建一個名爲handleSubmit
的函數,用來更新 state
的數據(添加表單提交的數據):
handleSubmit = (character) => {
// 添加數據
this.setState({
characters: [...this.state.characters, character],
});
};
將其傳給 Form 組件:
render() {
// 提取數據
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
</div>
);
}
接下來,在 Form.js 中創建名爲 submitForm()
,該方法將調用 父組件傳遞的方法 handleSubmit
。將Form組件的 state
作爲參數傳入:
修改 Form,js:
// 提交表單
submitForm = () => {
// 添加數據
this.props.handleSubmit(this.state);
// 添加之後清空state
this.setState(this.initialState);
};
最後,我們爲表單添加一個提交按鈕用來提交表單:
<input type="button" value="submit" onClick={this.submitForm} />
⚠️注意:這裏的submitFrom
方法也得綁定this
。
運行項目,具體的功能就實現了:
條件渲染
React 中的條件渲染和javaScript 中的一樣,可以使用 運算符 if 或者條件運算。
下面實現一個登錄/登出按鈕,根據不同的狀態現實不同的效果。爲了方便,還是在上面的項目實現這效果。
添加一個Login.js文件:
-
創建兩個組件:
LoginButton
和LogoutButton
分別代表登錄和註銷。// 登錄 const LoginButton = (props) => { return <button onClick={props.click}>Login</button>; }; // 註銷 const LogoutButton = (props) => { return <button onClick={props.click}>Logout</button>; };
-
創建一個有狀態的組件
LoginControl
,用來根據當前的狀態來渲染上面的兩個組件。
class LoginControl extends Component {
// 初始狀態
state = {
isLoggedIn: false,
};
handleLoginClick = () => {
this.setState({
isLoggedIn: true,
});
};
handleLogoutClick = () => {
this.setState({
isLoggedIn: false,
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
// 通過if 語句來進行條件渲染
if (isLoggedIn) {
button = <LoginButton click={this.handleLogoutClick} />;
} else {
button = <LogoutButton click={this.handleLoginClick} />;
}
return (
<div>
{button}
</div>
);
}
}
- 爲了更好的看到效果,我們每次切換後,加上對應的描述。創建兩個組件,代表切換後對應的描述:
// 登錄
const LoginGreeting = () => {
return <span>Welcome </span>;
};
// 註銷
const LogoutGreeting = () => {
return <span>Please sign up</span>;
};
- 創建一個組件
Geeting
,來渲染上面的描述組件.這裏使用三目運算符來實現
const Greeting = (props) => {
const { isLoggedIn } = props;
return <div>{isLoggedIn ? <LoginGreeting /> : <LogoutGreeting />}</div>;
};
- 最後,在
LoginControl
組件中加入這個Greeting
組件,同時在App 組件中加入LoginControl
組件。
// ...
return (
<div>
{button}
<Greeting isLoggedIn={isLoggedIn} />
</div>
// ...
在App
加入LoginControl
組件:
// ...
render() {
// 提取數據
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
<LoginControl />
</div>
);
}
// ...
運行項目(樣式有點醜~):
列表 & key
前面的例子 TableBody
組件中 ,已經接觸了列表:
const TableBody = (props) => {
const rows = props.characterData.map((item, index) => {
return (
<tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={() => props.removeCharacter(index)}>Dalete</button>
</td>
</tr>
);
});
return <tbody>{rows}</tbody>;
};
這個組件中,rows
爲 tr
的數組,最後將rows
插入到 < tbody>
元素中。
這裏可以看到每一個 tr
都有一個特殊的屬性key
,這個key
是必要的(這與diff算法有關)。
key
幫助 React
識別哪些元素改變了,比如刪除或者添加。所以應當給數組中的每一個元素給予一個確定的標識。一個元素的key
最好是這個元素在列表中擁有的獨一無二的字符串。通常使用數據的id
來作爲元素的 key
。如果元素沒有確定的 id
時,可以使用元素索引 index
作爲 key
。
⚠️注意:
- 元素的
key
只有放在就近的數組上下文中才有意義。如上例中的map 方法中的元素需要設置 key 屬性 key
值只是在兄弟節點間獨一無二,而不是全局的唯一。(也就是說,生成兩個不同的數組時,可以使用相同的key
)
生命週期
掛載
當組件實例被創建並插入 DOM中,其生命週期調用順序如下:
- constructor: 構造函數初始化,最先被執行,初始化
state
等 - getDerivedStateFromProps :這是一個靜態方法,需要在前面添加
static
屬性 - render: 渲染函數,返回渲染的內容,當頁面更新時也會觸發
- componentDidMount: 組件掛載之後,這個時候組件已經掛載完畢了
更新
- getDerivedStateFromProps : 組件即將被更新,這裏參數分別對應前後被修改的內容,通過返回一個布爾值告知是否要更新視圖。
- render: 當視圖更新,那麼render 也會更新
- getSnapshotBeforeUpdate:
getSnapshotBeforeUpdate
在render
之後componentDidUpdate
之前輸出,類似於中間件用來做一些捕獲操作 - componentDidUpdate:
getSnapshotBeforeUpdate
,有三個參數prevProps
,prevState
,snapshot
,表示之前的props
,之前的state
,和snapshot
。snapshot
是getSnapshotBeforeUpdate
返回的值
卸載
- componentWillUnmount: 組件卸載,可以清除一些定時器,取消網絡請求。
修改 App.js 代碼:
class App extends Component {
constructor() {
super();
console.log('1: constructor初始化');
}
state = {
characters: [
{
name: 'Charlie',
job: 'Janitor',
},
{
name: 'Mac',
job: 'Bouncer',
},
{
name: 'Dee',
job: 'Aspring actress',
},
],
};
// 移除項目
removeCharacter = (i) => {
this.setState({
characters: this.state.characters.filter((item, index) => {
return i !== index;
}),
});
};
handleSubmit = (character) => {
// 添加數據
this.setState({
characters: [...this.state.characters, character],
});
};
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps: 組件將要被更新');
console.log(prevState);
return true;
}
componentDidMount() {
console.log('componentDidMount 組件掛載完畢');
}
getSnapshotBeforeUpdate(preProps, prevState) {
console.log(preProps, prevState);
return 'top: 200';
}
componentDidUpdate(preProps, prevState, snapshot) {
console.log('組件更新完畢');
console.log(preProps, prevState, snapshot);
}
render() {
// 提取數據
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
<LoginControl />
</div>
);
}
}
運行結果:
總結
學習React很多都是以Vue的思路先入個門,很多東西都是類似的。第一篇入門文章就到這裏,後面繼續~~。