[TOC]
# 組件
## 定義組件
1.createClass 工廠方法
```js
const Button == React.createClass({
render() {
return <button />
}
})
```
以上代碼創建了一個按鈕組件,并且應用的其他組件也可以引用它
可以用純 JavaScript 對其進行改寫,如下所示
```js
const Button = React.createClass({
render() {
return React.createElement('button')
}
})
```
無須用 Babel 進行轉譯即可在任何地方運行以上代碼。
2.繼承 React.component(推薦)
使用以下代碼重寫上例中的按鈕
```js
class Button extends React.component {
render() {
return <button />
}
}
```
這兩種方法的區別是什么呢?除了語法上的差異,其還有如下的區別:
<span style="font-size: 20px; color: #ff502c;">1.prop</span>
第一個區別在于如何定義組件期望接收的 prop 及其默認值
createClass 方法需要在作為參數傳入函數的對象內定義 prop,同時在 getDefaultProps 內返回默認值
```js
const Button = React.createClass({
propTypes: { // 該屬性列出了能夠傳遞給該組件的所有值
text: React.PropTypes.string
},
// 定義了 prop 的默認值,如果父組件中傳遞了 prop,那么對應的默認值會被覆蓋
getDefaultProps() {
return {
text: 'Click me'
}
},
render() {
return <button>{this.props.text}</button>
}
})
```
可以用類實現同樣的目的:
```js
class Button extends React.Component {
render() {
return <button>{this.props.text}</button>
}
Button.propTypes = {
text: React.PropTypes.string
}
Button.defaultProps = {
text: 'Click me'
}
}
```
<span style="font-size: 20px; color: #ff502c;">2.狀態</span>
createClass 工廠方法和 extends React.Component 方法的另一個重大區別是,組件初始狀態的定義方式不同。
和前面一樣,使用 createClass 需要調用函數,而使用類則需要設置實例的屬性
來看一個示例:
```js
const Button = React.createClass({
getInitialState() {
return {
text: 'Click me!'
}
},
render() {
return <button>{this.state.text}</button>
}
})
```
getInitialState 方法期望返回一個對象,該對象包含每個狀態屬性的默認值。
如果用類來定義初始狀態,則需要在類的構造器方法內設置實例的狀態屬性:
```js
class Button extends React.Component {
constructor(props) {
super(props)
this.state = {
text: 'Click me!'
}
}
render() {
return <button>{this.state.text}</button>
}
}
```
定義狀態的這兩種方式等效,使用類的好處是無須使用 React 特有的 API,直接在實例上定義屬性即可
<span style="font-size: 20px; color: #ff502c;">3.自動綁定</span>
createClass 有一項非常方便的特性,但該特性也會隱藏 JavaScript 的工作原理,從而造成誤解。這項特性允許我們創建事件處理器,并且當調用事件處理器時 this 會指向組件本身:
```js
const Button = React.createClass({
handleClick() {
console.log(this)
},
render() {
return <button onClick={this.handleClick} />
}
})
```
createClass 允許我們按照以上方式設置事件處理器,這樣一來,函數內部的 this 就會指向組件本身。這允許我們調用同一組件實例的其他方法。例如,調用 this.setState() 等其他方法所產生的結果都能符合預期。
現在我們來看看 this 在類中的差別以及如何才能實現同樣的行為:
```js
class Button extends React.Component {
handleClick() {
console.log(this)
}
render() {
return <button onClick={() => this.handleClick()} />
}
}
```
方案之一就是使用箭頭函數,但是在渲染方法中綁定函數會帶來無法預料的副作用,因為每次渲染組件(應用在生命周期內會多次渲染組件)時都會觸發箭頭函數。
解決這一問題的最佳方案是在構造器內進行綁定操作,這樣即使多次渲染組件,它也不會發生任何改變
```js
class Button extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this)
}
render() {
return <button onClick={() => this.handleClick()} />
}
}
```
如果覺得 bind 很麻煩,React 也提供了如下的 “實驗性” 語法
```
class LoggingButton extends React.Component {
// 此語法確保 `handleClick` 內的 `this` 已被綁定。
// 注意: 這是 *實驗性* 語法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
```
## 函數組件
定義組件最簡單的方式就是編寫 JavaScript 函數:
```js
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;
}
```
該函數是一個有效的 React 組件,因為它接收唯一帶有數據的 “props”(代表屬性)對象與并返回一個 React 元素。這類組件被稱為“函數組件”,因為它本質上就是 JavaScript 函數。相比于使用 class 來定義組件,我們不需要考慮函數組件的狀態和生命周期等特性。
## state 與 prop
<span style="font-size: 20px;">props 的只讀性</span>
組件無論是使用函數聲明還是通過 class 聲明,都決不能修改自身的 props。來看下這個`sum`函數
```js
function sum (a, b) {
return a + b;
}
```
這樣的函數被稱為 <span style="font-family:楷體;font-weight: 700;">純函數</span>,因為該函數不會嘗試更改入參,且多次調用下相同的入參始終返回相同的結果。
相反,下面這個函數則不是純函數,因為它更改了自己的入參:
```js
function withdraw(account, amount) {
account.total -= amount;
}
```
React 非常靈活,但它也有一個嚴格的規則:
> 所有 React 組件都必須像純函數一樣保護它們的 props 不被更改。
當然,應用程序的 UI 是動態的,并會伴隨著時間的推移而變化。因此就有了 “state”。在不違反上述規則的情況下,state 允許 React 組件隨用戶操作、網絡響應或者其他變化而動態更改輸出內容。
<span style="font-size: 20px;">state</span>
React 使用狀態來生成新的 UI
- 訪問狀態: state 對象是組件的屬性對象,可以通過 this 引用來訪問,例如`this.state.name`
- 初始化狀態:在組件的 constructor() 方法中直接設置 this.state 的初始值
- 更新狀態:切忌使用 this.state = xxx 來更新狀態,需要使用類方法 this.setState(data, callback) 來改變狀態,React 會將 data 和當前狀態 **合并(部分)**,然后調用 render() 方法。之后 React 會調用 callback。
為 setState() 方法添加 callback 參數非常重要,因為該方法可以被異步調用。如果依賴新的狀態,可以使用回調來確保這個新的狀態可用。
<span style="font-size: 20px;">state 與 props</span>
this.state 和 this.props 的區別大致如下:
① 前者可變,后者不可變(組件不應該修改自身的 props)
② 屬性(props)從父組件傳遞,狀態在組件內部而非父組件中定義;即屬性只能從父組件變更,而不能從組件本身變更。

<span style="font-size: 20px;">setState 注意事項</span>
Ⅰ. 不要直接修改 state,例如,此代碼不會重新渲染組件:
```js
// Wrong
this.state.comment = 'Hello';
```
而是應該使用?setState():
```js
// Correct
this.setState({comment: 'Hello'});
```
構造函數是唯一可以給 `this.state` 賦值的地方:
<br />
Ⅱ. state 的更新可能是異步的
出于性能考慮,React 可能會把多個?setState()?調用合并成一個調用。
因為?this.props?和?this.state?可能會異步更新,所以你不要依賴他們的值來更新下一個狀態。例如,此代碼可能會無法更新計數器:
```js
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
```
要解決這個問題,可以讓`setState()`接收一個函數而不是一個對象。這個函數用上一個 state 作為第一個參數,將此次更新被應用時的 props 做為第二個參數:
```js
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
// 與下面的代碼等價
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
```
## 組件通信
參考鏈接:[https://www.jianshu.com/p/fb915d9c99c4](https://www.jianshu.com/p/fb915d9c99c4)
Ⅰ. 父子組件通信
父組件可以通過 prop 向子組件傳遞數據和回調函數,子組件可以利用回調函數來與父組件通信。
```js
// App.js
import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";
export default class App extends Component{
callback(msg){
console.log(msg);
}
render(){
return(
<div>
<Sub callback = { this.callback.bind(this) } />
</div>
)
}
}
```
```js
// SubComponent.js
import React from "react";
const Sub = (props) => {
const cb = (msg) => {
return () => {
props.callback(msg)
}
}
return(
<div>
<button onClick = { cb("我們通信吧") }>點擊我</button>
</div>
)
}
```
Ⅱ. 跨級組件通信(嵌套關系)
所謂跨級組件通信,就是父組件向子組件的子組件通信,向更深層的子組件通信。跨級組件通信可以采用下面兩種方式:
- 中間組件層層傳遞 props
- 使用 context 對象
第一種方式在組件嵌套層次在三層之內還可以考慮,組件嵌套過深時顯然是不可行的。
context 相當于一個全局變量,是一個大容器,我們可以把要通信的內容放在這個容器中,這樣一來,不管嵌套有多深,都可以隨意取用。
使用 context 也很簡單,需要滿足兩個條件:
- 上級組件要聲明自己支持 context,并提供一個函數來返回相應的 context 對象
- 子組件要聲明自己需要使用 context
具體使用見參考鏈接。
Ⅲ. 沒有嵌套關系的組件之間的通信
非嵌套組件,就是沒有任何包含關系的組件,包括兄弟組件以及不在同一個父級中的非兄弟組件。對于非嵌套組件,可以采用下面兩種方式:
- 利用二者共同父組件的 context 對象進行通信
- 使用自定義事件的方式
前者會增加子組件和父組件的耦合度,且如何找公共父組件也是一個問題;后者類似中介者模式 + 發布-訂閱 ? 就類似 Vue 的中央事件總線 bus 一樣吧。
這里只記下思路,具體代碼見參考鏈接。一般都直接上 Redux 了管他那么多...
# 生命周期
## 老版本的生命周期
**所謂生命周期函數即在某一時刻會被組件自動調用的函數**
React 提供了一種基于生命周期事件的方法來控制和自定義組件行為。這些事件可以歸為以下幾類:
- 掛載事件(僅調用一次):發生在 React 元素(組件類的實例)被綁定到真實 DOM 節點上時
- 更新事件(調用多次):發生在 React 元素有新的屬性或狀態需要更新時
- 卸載事件(僅調用一次):發生在 React 元素從 DOM 中卸載時
constructor 在某種意義上也算掛載事件因為其只會執行一次

<span style="font-size:20px; ">掛載</span>
`componentWillMount()`發生在掛載到 DOM 之前
`componentDidMount()`發生在掛載和渲染之后。推薦放置一些和其它框架與庫集成的代碼,以及向服務器發送 XHR 請求。因為此時組件已經存在于 DOM 中,所有元素(包括子節點)都已經可以訪問。
在同構代碼(在服務器和瀏覽器中使用相同的組件)中也非常有用,可以在該方法中添加僅瀏覽器端的邏輯,并確保只會在瀏覽器中調用,而不在服務器端調用。
<span style="font-size:20px; ">更新</span>
**props 發生變化和 states 發生變化都會觸發更新**
`componentWillReceiveProps(nextProps)`發生在組件即將接收屬性時,如果組件沒有 props 參數那么該生命周期函數不會被調用;只要父組件的 render 函數被重新執行了,子組件的該生命周期函數就會被執行(mount 時不算?)
`shouldComponentUpdate(nextProps, nextState)`通過判斷何時需要更新、何時不需要更新、允許對組件的渲染進行優化,返回值為一個布爾值(如果使用了,可以自己定義是否更新)
`componentWillUpdate(nextProps, nextState)`發生在組件將要更新之前
`componentDidUpdate(prevProps, prevState)`發生在組件更新完成之后
<span style="font-size:20px; ">卸載</span>
`componentWillUnmount()`允許在組件卸載之前解綁所有的事件監聽器或者做其他清理操作。
另外,` this.forceUpdate() `會強制更新,可以在因為各種原因導致更新狀態或屬性不會觸發期望的重新渲染時使用。例如,這可能發生在 render() 中使用不屬于狀態和屬性的數據,那么數據發生變更時,這種情形下需要手動觸發更新。一般情況下應該避免使用。
## 新的生命周期

1. React16 新的生命周期棄用了 componentWillMount、componentWillReceivePorps,componentWillUpdate
2. 新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate 來代替棄用的三個鉤子函數(componentWillMount、componentWillReceivePorps,componentWillUpdate)
3. React16 并沒有刪除這三個鉤子函數,但是不能和新增的鉤子函數(getDerivedStateFromProps、getSnapshotBeforeUpdate)混用,React17 將會刪除 componentWillMount、componentWillReceivePorps,componentWillUpdate
4. 新增了對錯誤的處理(componentDidCatch)
- `getDerivedStateFromProps(props, state)`
組件每次被 render 的時候,包括在組件構建之后(虛擬 dom 之后,實際 dom 掛載之前),每次獲取新的 props 或 state 之后;每次接收新的 props 之后都會返回一個對象作為新的 state,返回 null 則說明不需要更新 state;配合 componentDidUpdate,可以覆蓋 componentWillReceiveProps 的所有用法
- `getSnapshotBeforeUpdate(prevProps, prevState)`
觸發時間: update 發生的時候,在 render 之后,在組件 dom 渲染之前;返回一個值,作為 componentDidUpdate 的第三個參數;配合 componentDidUpdate, 可以覆蓋 componentWillUpdate 的所有用法
## 生命周期開發中的應用
⒈考慮這么一種情況,父組件的 render 函數被執行了,那么其子組件的 render 函數也會被執行(即使其 props 沒有任何改變);針對這么一種情況,可以使用 shouldComponentUpdate() 這一生命周期函數
```js
shouldComponentUpdate(nextProps, nextState)
if (nextProps.xxx !== this.props.xxx) return true // 下一“狀態”的 props 和當前的比較
else return false
```
⒉AJAX 請求的位置
AJAX 請求一般放在 ComponentDidMount 生命周期函數中(只執行一次的)
# 事件處理
在循環中,通常我們會為事件處理函數傳遞額外的參數。例如,若 `id` 是你要刪除那一行的 ID,以下兩種方式都可以向事件處理函數傳遞參數:
```js
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
```
上述兩種方式是等價的,分別通過`箭頭函數`和`Function.prototype.bind`來實現。
在這兩種情況下,React 的事件對象`e`會被作為第二個參數傳遞。如果通過箭頭函數的方式,事件對象必須顯式的進行傳遞,而通過`bind`的方式,事件對象以及更多的參數將會被隱式的進行傳遞。
# 組合(相當于 Vue 的 Slot)
有些組件無法提前知曉它們子組件的具體內容。在`Sidebar`(側邊欄)和`Dialog`(對話框)等展現通用容器(box)的組件中特別容易遇到這種情況。
這些組件可以使用一個特殊的`children prop` 來將他們的子組件傳遞到渲染結果中:
```js
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
```
這使得別的組件可以通過 JSX 嵌套,將任意組件作為子組件傳遞給它們。
```js
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
```
`<FancyBorder>`JSX 標簽中的所有內容都會作為一個`children prop` 傳遞給`FancyBorder`組件。因為`FancyBorder`將`{props.children}`渲染在一個`<div>`中,被傳遞的這些子組件最終都會出現在輸出結果中。
*****
少數情況下,你可能需要在一個組件中預留出幾個“洞”。這種情況下,我們可以不使用`children`,而是自行約定:將所需內容傳入 props,并使用相應的 prop。
```js
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
```
# 高級指引
這部分內容整理自:[https://react.docschina.org/docs/code-splitting.html](https://react.docschina.org/docs/code-splitting.html)
## 代碼分割
通過動態`import()`語法實現代碼分割,當 Webpack 解析到該語法時,它會自動地進行代碼分割。
```js
// 使用之前
import { add } from './math';
console.log(add(16, 26));
```
```js
// 使用之后
import("./math").then(math => {
console.log(math.add(16, 26));
});
```
使用 Create React App 該功能已配置好,如果自己配置 Webpack 需要配置代碼分割用到的插件以及讓 Babel 能解析動態 import 語法而不是將其轉換。
<span style="font-size: 20px;">React.lazy</span>
`React.lazy`函數能讓你像渲染常規組件一樣處理動態引入(的組件)。
```js
// 使用之前
import OtherComponent from './OtherComponent';
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
```
```js
// 使用之后
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
```
這個代碼將會在渲染組件時,自動導入包含`OtherComponent`組件的包。
`React.lazy`接受一個函數,這個函數需要動態調用`import()`。它必須返回一個`Promise`,該 Promise 需要 resolve 一個`defalut`export 的 React 組件。
<span style="font-size: 20px;">Suspense</span>
如果在`MyComponent`渲染完成后,包含`OtherComponent`的模塊還沒有被加載完成,我們可以使用加載指示器為此組件做優雅降級。這里我們使用`Suspense`組件來解決。
```js
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
```
`fallback`屬性接受任何在組件加載過程中你想展示的 React 元素。你可以將`Suspense`組件置于懶加載組件之上的任何位置。你甚至可以用一個`Suspense`組件包裹多個懶加載組件。
```js
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
```
<span style="font-size: 20px;">基于路由的代碼分割</span>
使用`React.lazy`和`React Router`來配置基于路由的代碼分割
```js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
```
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs