(一)React 簡(jiǎn)介
- 主要作用是為 MVC 模式中的視圖(view)層構(gòu)建界面視圖
- 還可以以插件的形式作用于 Web 應(yīng)用程序的非視圖部分邀泉,實(shí)現(xiàn)與其他 JavaScript 框架的整合
DOM发皿,文檔對(duì)象模式
W3C組織推薦的處理可擴(kuò)展標(biāo)志語(yǔ)言的標(biāo)準(zhǔn)編程接口
1. 傳統(tǒng)的HTML 網(wǎng)頁(yè)開(kāi)發(fā)
- 直接操作 DOM,需要非常大的開(kāi)銷郁妈;
- 更新頁(yè)面內(nèi)容或元素,需要將整個(gè)頁(yè)面重新繪制;
2. React 性能優(yōu)化方案(刷新邏輯)
- React 底層設(shè)計(jì)了一個(gè)虛擬的 DOM;
- 虛擬的 DOM 與頁(yè)面真實(shí)的 DOM 進(jìn)行映射躁愿;
- 當(dāng)數(shù)據(jù)變化時(shí)
- React 會(huì)重新構(gòu)建 DOM 樹(shù);
- 通過(guò)底層的 diff 算法找到 DOM 的差異部分沪蓬;
- 瀏覽器只需要更新變化的部分彤钟;
3. React 的跨平臺(tái)方案
借助虛擬的 DOM 技術(shù)來(lái)實(shí)現(xiàn)服務(wù)端應(yīng)用、Web 應(yīng)用和移動(dòng)手機(jī)應(yīng)用的跨平臺(tái)開(kāi)發(fā)
4. React 數(shù)據(jù)的單向流向
- 數(shù)據(jù)默認(rèn)從父節(jié)點(diǎn)傳到子節(jié)點(diǎn)怜跑;
- 父節(jié)點(diǎn)數(shù)據(jù)通過(guò) props 傳遞到子節(jié)點(diǎn)样勃,如果父節(jié)點(diǎn)的 props 值發(fā)生改變吠勘,那么其所有子節(jié)點(diǎn)也會(huì)執(zhí)行重新渲染操作;
- 好處:使得組件足夠扁平峡眶,更加便于維護(hù)剧防。
(二)React 組件詳解
2.1 React 組件基礎(chǔ)知識(shí)
組件定義
- 組件是 React 的核心內(nèi)容
- 組件是視圖頁(yè)面的重要組成部分
- 每一個(gè)視圖頁(yè)面都由一個(gè)或多個(gè)組件構(gòu)成
- 組件是 React 應(yīng)用程序的基石
組件分類:無(wú)狀態(tài)組件
沒(méi)有狀態(tài)的組件,只做純靜態(tài)展示
- 無(wú)狀態(tài)組件是最基本的組件存在形式
- 構(gòu)成:由 props屬性 和 render渲染函數(shù) 構(gòu)成
- 好處:由于不涉及狀態(tài)的更新辫樱,復(fù)用性最強(qiáng)
組件分類:有狀態(tài)組件
在無(wú)狀態(tài)的組件的基礎(chǔ)上增加了組件內(nèi)部狀態(tài)管理
- 生命周期:有狀態(tài)組件有生命周期峭拘,在不同的時(shí)刻觸發(fā)組件狀態(tài)的更新
- 用處:有狀態(tài)組件被大量使用在業(yè)務(wù)邏輯開(kāi)發(fā)中
組件的創(chuàng)建
- ES5 的 React.createClass 方式,逐漸被下面的 ES6 代替
- ES6 的 React.Component 方式
import React, { Component } from 'react';
class TextView extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>Text</div>
);
}
}
- 無(wú)狀態(tài)的函數(shù)組件方式(箭頭函數(shù)格式)
const Todo = (props) => {
<li
onClick={props.onClick}
style={{textDecoration: props.complete ? "line-through" : "none"}}>
{props.text}
</li>
}
const Todo = ({ onClick, complete, text, props }) => {
<li
onClick={props.onClick}
style={{textDecoration: props.complete ? "line-through" : "none"}}>
{props.text}
</li>
}
【注意】
- 無(wú)狀態(tài)組件一般會(huì)搭配
高階組件(OHC)
一起使用 - 高階組件主要用來(lái)托管
state
- Redux 框架就是通過(guò) store 來(lái)管理數(shù)據(jù)源和組件的所有狀態(tài)狮暑,其中所有負(fù)責(zé)展示的組件都使用無(wú)狀態(tài)函數(shù)式寫(xiě)法
- 無(wú)狀態(tài)組件被大規(guī)模應(yīng)用在大型應(yīng)用程序中
- 缺點(diǎn):無(wú)狀態(tài)組件在被 React 調(diào)用之前鸡挠,組件還沒(méi)有實(shí)例化,所以它不支持 ref 特性
2.2 props
-
props
是組件對(duì)外的接口搬男,一般情況下拣展,props是不變的 -
state
是組件對(duì)內(nèi)的接口
props 的使用方式
{this.props.key}
props 是父子組件交互的唯一方式:
super(props);
const { Component } = require("react");
class HelloMessage extends Component {
constructor(props) {
super(props);
this.state = {
name = 'Jack'
};
}
render() {
return (
<h1>Hello {this.props.name}</h1>
)
}
}
export default HelloMessage;
- 通過(guò)
{this.props.name}
方式獲取 props 中的值 - ES5的語(yǔ)法中,通過(guò)
getDefaultProps()
方法中設(shè)置默認(rèn)值
在子類中定義props
const { Component } = require("react");
export default class Child extends Component {
constructor(props) {
super(props);
this.state = {
counter:props.age||0
};
}
render() {
return (
<h1>Hello {this.props.name}</h1>
)
}
}
Child.PropTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
}
Child.defaultProps = {
age: 0
}
在父類中使用 props
export default class Father extends Comment {
render() {
return (
<div>
<Child name="Jack" age={20} />
<Child name="Tom" age={30} />
</div>
)
}
}
- 如果父類需要向子組件傳遞數(shù)據(jù)缔逛,只需要在組件中引入子組件备埃,然后使用組件提供的props屬性,即可向子組件傳遞數(shù)據(jù)
- 子組件 props 接受的數(shù)據(jù)格式由 PropTypes 進(jìn)行檢測(cè)
2.3 state
-
props
是組件對(duì)外的接口褐奴,一般情況下按脚,props是不變的,props對(duì)使用它的組件來(lái)說(shuō)是只讀的敦冬,如果要修改props辅搬,只能通過(guò)組件的父類組件修改
-
state
是組件對(duì)內(nèi)的接口,是組件的私有屬性
脖旱,只能被本組件訪問(wèn)和修改
組件“狀態(tài)機(jī)”
通過(guò)與用戶交互實(shí)現(xiàn)不同狀態(tài)堪遂,進(jìn)而渲染界面,讓用戶界面與數(shù)據(jù)保持一致
在 React 中萌庆,如果需要使用 state蚤氏,就需要在組件的 constructor 中初始化相關(guān)的 state
constructor(props) {
super(props);
this.state() = {
key:value,
...
};
}
setState():更新組件的state
this.setState({
key:value,
});
setState() 異步操作
- 更新的狀態(tài)不會(huì)立馬刷新,而是將修改的狀態(tài)放入一個(gè)
隊(duì)列
中踊兜; - React 可能會(huì)對(duì)多次 setState 狀態(tài)修改進(jìn)行
合并修正
竿滨; -
{this.state}
獲取的狀態(tài)可能會(huì)不準(zhǔn)確; - 也不能依賴
props
來(lái)計(jì)算組件的下一個(gè)狀態(tài)捏境;
setState 淺合并過(guò)程
在調(diào)用
setState
修改組件狀態(tài)時(shí)于游,只需要傳入需要改變的狀態(tài)變量即可,不需要傳入組件完整的state
title 和 content 屬性
this.state = {
title: 'Jack',
content: 'Welcome to React',
}
當(dāng)只需要修改 title 屬性時(shí)垫言,只在 setState() 中修改 title 即可
this.setState({
title: 'Tom',
});
- 修改了title
- content 保持原有狀態(tài)
淺合并之后的結(jié)果是
{
title: 'Tom',
content: 'Welcome to React',
}
2.4 ref
- 本質(zhì)就是調(diào)用 ReactDOM.render()返回的組件實(shí)例贰剥,用來(lái)表示對(duì)組件真正實(shí)例的引用。
- 具體使用時(shí)筷频,可以將它綁定到組件的 render() 上蚌成,然后就可以用它輸出組件的實(shí)例前痘。
- 可以使用 ref 方式來(lái)修改子組件。
- ref 不僅可以掛在到組件上担忧,還可以作用于具體的 DOM 元素芹缔。掛載到 DOM 元素時(shí),ref 可以表示具體的 DOM 元素節(jié)點(diǎn)瓶盛。
- ref 表示對(duì)組件實(shí)例的引用時(shí)最欠,不能再函數(shù)式組件內(nèi)上使用 ref 屬性。
- ref 調(diào)用方式:設(shè)置
回調(diào)函數(shù)
和字符串
的方式惩猫,官方推薦回調(diào)函數(shù)芝硬。
ref 調(diào)用方式:回調(diào)函數(shù)
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
isInputShow: false // 控制 input 是否渲染
};
}
inputRefcb(instance) {
if (instance) {
instance.focus();
}
}
render() {
{
this.state.isInputShow ?
<div>
<input ref={this.inputRefcb} type="text"/>
</div>
: null
}
}
}
觸發(fā)回調(diào)函數(shù)的時(shí)機(jī)
- 組件被渲染后,回調(diào)參數(shù) instance 作為 input 的組件實(shí)例的引用轧房,可以立即使用該組件
- 組件被卸載后拌阴,回調(diào)參數(shù) instance 此時(shí)為 null,這樣可以確保內(nèi)存不被泄露
- ref 屬性本身發(fā)生改變奶镶,原有的 ref 會(huì)再次被調(diào)用皮官,此時(shí)的回調(diào)參數(shù)instance 變成具體的組件實(shí)例
ref 調(diào)用方式:字符串
class Demo extends Component {
constructor(props) {
super(props);
}
onfocus() {
this.refs.inputRef.focus()
}
render() {
{
this.state.isInputShow ?
<div>
<input ref="inputRef" type="text"/>
<input type="button" value="Focus" onClick={this.onfocus}/>
</div>
: null
}
}
}
- 通過(guò)
this.refs.inputRef
來(lái)獲取組件實(shí)例 - 不能在函數(shù)式聲明組件中使用 ref,因?yàn)樗麄儾荒塬@取組件的實(shí)例
父組件訪問(wèn)子組件的 DOM 節(jié)點(diǎn)
function TextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
)
}
class Father extends Component {
render() {
return (
<TextInput inputRef={
e => this.inputElement = e
} />
);
}
}
訪問(wèn)過(guò)程:
- 在父組件 Father 中引用子組件 TextInput
- 子組件 TextInput 通過(guò) ref 傳入 inputRef 函數(shù)
- 子組件 TextInput 又將這個(gè)回調(diào)函數(shù)作為 input 元素的 ref 屬性
- 父組件 Father 可以通過(guò)
{this.inputElement}
獲取子組件的 input 對(duì)應(yīng)的 DOM 元素实辑。
(三)React 高階組件
3.1 定義與實(shí)現(xiàn)
- 高階組件是接受 React組件 作為參數(shù),并返回一個(gè)新的 React組件 的組件藻丢。
- 高階組件可以看做是對(duì)傳入的 React組件 經(jīng)過(guò)一系列處理剪撬,最后返回一個(gè)相對(duì)增強(qiáng)的 React組件
- 高階組件本質(zhì)上一個(gè)函數(shù),不是組件悠反,可以參考
鏈?zhǔn)骄幊?/code>
編寫(xiě)一個(gè)高階組件
- 接受一個(gè) WrappedComponent 組件
- 返回一個(gè) HOC 的 withHeader 組件
import React, {Component} from 'react';
export default function withHeader(WrappedComponent) {
return class HOC extends Component {
render() {
return <div>
<div className=" header">我是標(biāo)題</div>
</div>
}
}
}
高階組件也可以作為一個(gè)普通組件使用
@withHeader
export default class Demo extends Component {
render() {
return (
<div>我是一個(gè)普通組件</div>
);
}
}
@withHeader
是 ES7 中的裝飾器語(yǔ)法残黑,相當(dāng)于下面的表達(dá)式
const EnhanceDemo = withHeader(Demo);
如果在某個(gè)組件中多次重復(fù)使用同一個(gè)高階組件,在調(diào)試時(shí)就會(huì)看到一大堆相同的高階組件斋否,可以在使用時(shí)保留高階組的原有名稱來(lái)區(qū)分梨水。
3.2 分類
高階組件的實(shí)現(xiàn)方式:
屬性代理
和反向繼承
- 屬性代理:通過(guò)返回包括原組件并添加額外功能來(lái)實(shí)現(xiàn)高階組件,最常見(jiàn)
const Container = (WrappedComponent) => class extends Components {
render() {
const newProps = {
text: 'newText',
}
return <WrappedComponent> {...this.props} {...newProps} />
}
}
- 反向繼承:通過(guò)返回繼承原組件來(lái)控制 render 函數(shù)茵臭,進(jìn)而實(shí)現(xiàn)高階組件疫诽。相對(duì)于屬性代理,反向繼承能訪問(wèn)到的區(qū)域和權(quán)限更多
const Container = (WrappedComponent) => class extends WrappedComponent {
render() {
return super.render();
}
通過(guò)繼承 WrappedComponent旦委,可以使用 WrappedComponent 組件的 state奇徒、props、生命周期缨硝、render()等
3.3 命名與參數(shù)
displayName屬性
:當(dāng)高階組件包裹普通組件時(shí)摩钙,普通組件的名稱和靜態(tài)方法都會(huì)丟失,為了避免這種情況妒牙,給普通組件添加標(biāo)組組件名稱的displayName屬性
class HOC extends ...{
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
}
// getDisplayName 方法
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
'Component';
}
柯里化
:通過(guò)傳入不同的參數(shù)來(lái)得到不同的高階組件
function HOCFactoryFactory(...params) {
// 通過(guò)改變 params 來(lái)顯示不同結(jié)果
return class HOCFactory(WrappedComponent) {
render() {
return <WrappedComponent {...this.props} />
}
}
}
高階組件的缺點(diǎn)
可能會(huì)造成靜態(tài)方法丟失和 ref 屬性不能傳遞徒爹,所以在使用過(guò)程中需要遵循一下準(zhǔn)則
- 不要在組件的
render()
中使用高階組件,也盡量避免在組件的其他生命周期方法
中使用高階組件 - 如果需要使用被包裝組件的靜態(tài)方法竣稽,那么必須手動(dòng)賦值靜態(tài)方法
- ref 應(yīng)避免傳遞給包裝組件
(四)組件通信
4.1 父子組件通信
父組件 --> 子組件
父組件通過(guò) props 將值傳遞給子組件
- 父組件傳值:
params={this.state.params}
class Parent extends Component {
state = {
params: 'father send msg to child'
};
render() {
return <Child params={this.state.params} />;
}
}
- 子組件接收值:
{this.props.params}
class Child extends Component {
render() {
return <p>{this.props.params}</p>
}
}
子組件 --> 父組件
- 回調(diào)函數(shù)【最常見(jiàn)】
- 自定義事件
回調(diào)函數(shù)方式:
- 父組件將一個(gè)函數(shù)作為 props 傳遞給子組件长踊;
- 組組件調(diào)用該回調(diào)函數(shù)便可以向父組件傳值功舀;
class Parent extends Component {
constructor (props) {
super(props);
this.state = {
params: 'child send msg to father'
};
}
transMsg(types) {
console.log(type);
}
render() {
return <Child params={this.state.params} />;
}
}
class Child extends Component {
constructor(props) {
super(props);
console.log("params :", this.props.params);
this.props.transMsg("hi, fathre");
}
render() {
return <p>{this.state.params}</p>
}
}
4.2 跨級(jí)組件通信
父組件與子組件的子組件或者是更深層的子組件進(jìn)行的通信。實(shí)現(xiàn)方式有兩種:
- 使用組件 props 逐層傳遞
- 使用 context 對(duì)象傳遞
使用組件 props 逐層傳遞 的缺點(diǎn)
- 每一層都需要傳遞 props
- 增加程序的復(fù)雜度
實(shí)際開(kāi)發(fā)中不建議使用
使用 context 對(duì)象傳遞
- 父組件需要聲明支持 context之斯,并提供一個(gè)函數(shù)來(lái)返回相應(yīng)的 context 對(duì)象
- 子組件聲明需要使用的 context 對(duì)象日杈,并提供需要使用的 context 屬性的 PropTypes
代碼
export default class GrandSon extends Component {
// 子組件聲明自己需要使用的 context
static contextTypes = {
color : PropTypes.string,
};
static propTypes = {
value : PropTypes.string,
};
render() {
const { value } = this.props;
return (
<li style={{background: this.context.color}}>
<span>{value}</span>
</li>
);
}
}
export default class Father extends Component {
// 聲明自己要使用的 context
static fatherContextTypes = {
color : PropTypes.string,
};
static propTypes = {
name : PropTypes.string,
};
// 提供一個(gè)函數(shù), 用來(lái)返回 context 對(duì)象
getFatherContext() {
return {
color : 'red',
};
}
render () {
const {list} = this.props;
return (
<div>
<ul>
{
list.map((entry, index) =>
<GrandSon key={`list-${index}`} value={entry.text} />
)
}
</ul>
</div>
);
}
}
class GrandFather extends Component {
render() {
return(
<div>
<Father name='GrandFather'/>
</div>
);
}
}
如果組件中使用了構(gòu)造函數(shù),為了不影響跨級(jí)組件通信佑刷,還需要在構(gòu)造函數(shù)中傳入第二個(gè)參數(shù) context
constructor(props, context) {
super(props, context);
}
context 的缺點(diǎn)
因?yàn)閏ontext可以代表任何東西莉擒,所以它的類型是無(wú)法確定的,所以在使用的過(guò)程中也是需要謹(jǐn)慎對(duì)待
【總結(jié)】在父子組件通信模型中
- 父組件 -> 子組件:使用變量
- 子組件 -> 父組件:父組件提供回調(diào)函數(shù)瘫絮,子組件調(diào)用回調(diào)函數(shù)
其實(shí)如果將回調(diào)函數(shù)也看成一個(gè)屬性涨冀,那么這兩個(gè)過(guò)程其實(shí)都是一樣的,都是子組件使用父組件提供的“屬性”(變量或回調(diào)函數(shù))
4.3 非嵌套組件通信
沒(méi)有直接關(guān)系的兩個(gè)組件麦萤,例如兄弟組件(同一個(gè)父節(jié)點(diǎn)下的兩個(gè)節(jié)點(diǎn))鹿鳖、完全不相干的兩個(gè)組件。
對(duì)于兄弟組件壮莹,也是不可以直接通信的翅帜,可以通過(guò)狀態(tài)提升
來(lái)實(shí)現(xiàn)兄弟組件間的通信。提升狀態(tài)就是值通過(guò)父組件進(jìn)行中轉(zhuǎn)命满,但是當(dāng)層級(jí)較深時(shí)涝滴,中轉(zhuǎn)過(guò)程也會(huì)特別復(fù)雜,如何尋找公共父組件也是一個(gè)問(wèn)題胶台。
自定義事件
- 發(fā)布/訂閱模型
- 給事件對(duì)象上添加監(jiān)聽(tīng)器
- 通過(guò)觸發(fā)事件來(lái)實(shí)現(xiàn)組件之間的通信
- 在組件的
componentDidMount
中聲明自定義事件 - 在組件的
componentWillUnmount
中取消訂閱事件
安裝 events 模塊
通過(guò)自定義事件的方式來(lái)實(shí)現(xiàn)非嵌套組件間的通信歼疮,需要借助Node的events模塊,通過(guò)以下命令安裝 events 模塊
npm install events --save
然后在 src 目錄下創(chuàng)建一個(gè) events.js
文件
import { EventEmitter } from 'events';
export default new EventEmitter();
再創(chuàng)建一個(gè) ComponentA.js
文件
- 在組件
ComponentA.js
的componentDidMount
中聲明自定義事件 - 在組件
ComponentA.js
的componentWillUnmount
中取消訂閱事件
export default class ComponentA extends Component {
constructor(props) {
super(props);
this.state = {
message : 'ComponentA',
};
}
// 聲明一個(gè)自定義事件
componentDidMount() {
this.eventEmitter = events.addListener('changeMessage', (message) => {
this.setState ({
message,
});
});
}
// 取消事件訂閱
componentWillUnmount() {
events.removeListener(this.eventEmitter);
}
render() {
return (
<div>
{this.state.message}
</div>
);
}
}
再創(chuàng)建一個(gè)組件ComponentB
- 添加一個(gè)點(diǎn)擊事件
- ComponentA 接收到 ComponentB 發(fā)過(guò)來(lái)的消息后诈唬,刷新界面
// 組件 ComponentB
export default class ComponentB extends Component {
handleClick = (message) => {
events.emit('changeMessage', message);
};
render() {
return(
<div>
<button onClick={this.handleClick.bind(this, 'ComponentB')}>點(diǎn)擊發(fā)送信息</button>
</div>
);
}
}
創(chuàng)建測(cè)試用例韩脏,模擬兩個(gè)非嵌套組件的通信
// 測(cè)試用例
export default class AppTest extends Component {
render() {
return (
<div>
<ComponentA />
<ComponentB />
</div>
);
}
}
【總結(jié)】原生通知了解下
- 組件 ComponentA 監(jiān)聽(tīng)通知
- 組件 ComponentB 發(fā)送通知
(五)事件處理
5.1 事件監(jiān)聽(tīng)與處理
React 事件 和 HTML 事件
- React 事件使用駝峰命名法,而非全部小寫(xiě)
- React中铸磅,可以傳遞一個(gè)函數(shù)作為事件的處理函數(shù)赡矢,而非一個(gè)簡(jiǎn)單的字符串
為按鈕添加一個(gè)事件
- 只需要給 React 元素添加 onClick、onKeyDown 函數(shù)即可
class demo extends Component {
handleClick() {
console.log('Click me')
}
render() {
return(
<button onClick={this.handleClick}>React 實(shí)戰(zhàn)</button>
);
}
}
事件攔截
HTML 中通過(guò)
return false
來(lái)攔截事件
<a href="#" onclick="console.log('The link was clicked"); return false">
Click me
</a>
React 使用虛擬DOM基礎(chǔ)上實(shí)現(xiàn)的合成事件
SyntheicEvent
- React 的時(shí)間處理程序接受的是
SyntheicEvent
實(shí)例 -
stopPrepagation()
:阻止時(shí)間傳遞阅仔,目的是不讓事件分派到其他的 Document 節(jié)點(diǎn)济竹,但是默認(rèn)事件依然會(huì)執(zhí)行 -
preventDefault()
:通知瀏覽器不要執(zhí)行與事件關(guān)聯(lián)的默認(rèn)動(dòng)作,但事件依然會(huì)繼續(xù)傳遞霎槐。
function ActionLink() {
function handleClick(e) {
e.prevenDefault();
}
return (
<a href="#" onClick={handleClick}>Click me</a>
);
}
5.2 event 事件與 this 關(guān)鍵字
event 事件
- React 在虛擬 DOM 的基礎(chǔ)上實(shí)現(xiàn)的一套合成事件
- 處理監(jiān)聽(tīng)時(shí)送浊,需要傳入一個(gè) event 對(duì)象
- 完全符合 W3C 標(biāo)準(zhǔn),所以可以完全兼容瀏覽器丘跌,并擁有和瀏覽器一樣的事件接口
案例一:輸出按鈕的 innerHTML
class Demo extends Component {
handleClick(e) {
console.log(e.target.innerHTML)
}
render() {
return(
<button onClick={this.handleClick}>React 實(shí)戰(zhàn)</button>
);
}
}
函數(shù)與對(duì)象方法
先來(lái)看一個(gè)例子袭景,在上述方法中唁桩,如果輸出 this,this結(jié)果是 null
或者 undefined
handleClick(e) {
console.log(this) // `null` 或者 `undefined`
}
原因
: handleClick
是一個(gè)函數(shù)耸棒,并非是通過(guò)對(duì)象的方法調(diào)用的荒澡,而是直接的函數(shù)調(diào)用,所以在這個(gè)函數(shù)中与殃,就無(wú)法獲取到 this 所代表的類實(shí)例
解決辦法
:將函數(shù)綁定到當(dāng)前實(shí)例上
render() {
return(
<button onClick={this.handleClick.bind(this)}>React 實(shí)戰(zhàn)</button>
);
}
bind方法
- bind方式實(shí)現(xiàn)時(shí)間監(jiān)聽(tīng)非常常見(jiàn)单山;
- bind是React在ES5引入的事件監(jiān)聽(tīng)機(jī)制;
- bind格式:
Function.prototype.bind()
bind原理
- 當(dāng)調(diào)用函數(shù)對(duì)象的 bind() 方法時(shí)
- 系統(tǒng)會(huì)重新創(chuàng)建一個(gè)函數(shù)幅疼,新函數(shù)的行為和原函數(shù)一樣
- 因?yàn)樗麄兪怯芍付ǖ?this 和初始化參數(shù)構(gòu)造的原函數(shù)
bind傳參
- 格式:bind(this, arg1, arg2, ...)
function f() {
return this.a;
}
var g = f.bind({a:"azertyp"});
console.log(g()); //azertyp
var h = g.bind({a:"yoo"}); // bind 只生效一次
console.log(h()); //azertyp
5.3 EventEmitter 在 React Native 中的應(yīng)用
EventEmitter 是用來(lái)處理原生和 React Native 之間通信的
iOS原生和 JavaScript 層交互關(guān)系表
原始端函數(shù) | JavaScript 層接口 |
---|---|
sendEventWithName | RCTAppEventEmitter |
sendDeviceEventWithName | RCTDeviceEventEmitter |
sendInputEventWithName | RCTInputEventEmitter |
- iOS 中使用
RCTEventEmitter
- Android 中使用
RCTDeviceEventEmitter
iOS 和 React Native 交互
iOS 中通過(guò) eventDispatcher
的 sendAppEventWithName
方法將消息傳遞個(gè) JavaScript
#import "CalendarManager.h"
#import "RCTEventDispatcher.h"
@implementation CalendarManager
@synchesize bridge=_bridge;
-(void)calendarEventReminderReceived:(NSNotification *)notification {
NSString* eventName = notification.userInfo[@"name"];
[self.bridge.eventDispatcher sendAppEventWithName"EventReminder" body:@{@"name" : eventName}];
}
@end
JavaScript
通過(guò) addListener
訂閱該事件米奸,注意保持 name 一致,iOS中發(fā)出來(lái)的name是 EventReminder
爽篷,所以addListener監(jiān)聽(tīng)的也應(yīng)該是 EventReminder
悴晰。
在事件使用完之后取消事件的訂閱,即在 conponentWillUnmount
聲明周期函數(shù)中取消事件的訂閱逐工。
import { NativeAppEventEmitter, NativeEventEmitter } from 'react-native';
var subscription = NativeEventEmitter.addListener(
'EventReminder', (reminder) => console.log(reminder.name)
);
...
// 取消訂閱事件
conponentWillUnmount() {
subscription.remove();
}
Android 和 React Native 交互
Android中铡溪,通過(guò) RCTDeviceEventEmitter
來(lái)注冊(cè)事件
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("EventReminder", null);
(六)React Hook
6.1 Hook 簡(jiǎn)介
-
React Hook
是為了解決 React 的狀態(tài)共享
問(wèn)題; -
狀態(tài)共享
也可以看成是邏輯復(fù)用的問(wèn)題泪喊; - 因?yàn)?
React Hook
治共享數(shù)據(jù)處理邏輯棕硫,并不會(huì)共享數(shù)據(jù)本身;
在 React 應(yīng)用開(kāi)發(fā)中袒啼,狀態(tài)管理是組價(jià)你開(kāi)發(fā)必不可少的內(nèi)容哈扮。狀態(tài)管理的方式:
- 使用類組件
- 使用 redux 等狀態(tài)管理框架
案例【以前的做法】
class Example extends Component {
constructor(props) {
super(props);
this.state = {
count : 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={()=>this.state({ count: this.state.count+1 })}>Click me</button>
</div>
);
}
}
案例【現(xiàn)在的做法】
- 使用 React Hook 提供的 State Hook 來(lái)處理狀態(tài);
- 針對(duì)已經(jīng)存在的類組件瘤泪,也可以使用 State Hook 很好的進(jìn)行重構(gòu);
- Example 變成了一個(gè)函數(shù)組件育八,有自己的狀態(tài)对途,還可以更新自己的狀態(tài);
-
useState函數(shù)
是 React 的一個(gè) hook 函數(shù)髓棋,它的作用是聲明狀態(tài)變量实檀。
function Hook() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={()=>this.state({ count: this.state.count+1 })}>Click me</button>
</div>
);
}
6.2 Hook API
Hook API 背景故事
1. 如何解決狀態(tài)組件的復(fù)用問(wèn)題?
一般都是通過(guò)自上而下傳遞的數(shù)據(jù)流來(lái)將大型的視圖拆分成獨(dú)立的可復(fù)用組件按声。但是在實(shí)際開(kāi)發(fā)中膳犹,如何復(fù)用一個(gè)帶有業(yè)務(wù)邏輯的組件讓讓是一個(gè)問(wèn)題。
2. 函數(shù)組件和類組件
前面介紹了他們的一些特性:函數(shù)組件缺少組件的
狀態(tài)
签则、生命周期
等特征须床,所以一直不受青睞但是
Hool API
賦予了函數(shù)組件這些能力
React 提供了三個(gè)核心的 API
-
State API
:狀態(tài)API -
Effect API
:聲明周期API -
Custom API
:自定義API
useState 組件
用來(lái)定義和管理本地狀態(tài)
下面看一個(gè)計(jì)數(shù)器的小案例
- 函數(shù)組件的對(duì)象也可以是基礎(chǔ)類型值
- useState 返回的是一個(gè)數(shù)組,數(shù)組的第一個(gè)對(duì)象表示當(dāng)前狀態(tài)的值渐裂,第二個(gè)對(duì)象表示用于更改狀態(tài)的函數(shù)豺旬,類似于類組件的 setState
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={ () => setCount(count+1)}>+</button>
<span>{count}</span>
<button onClick={ () => setCount(count-1)}>-</button>
</div>
)
}
export default App;
useState
的聲明方式
- 單次聲明多個(gè)對(duì)象
const [count, setCount] = useState({
count1: 0,
coutn2: 0
});
- 多次聲明多個(gè)對(duì)象
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
在實(shí)際使用中钠惩,多次聲明
會(huì)更加方便,因?yàn)楦潞瘮?shù)采用的是替換而不是合并族阅。
如果要處理嵌套多層的數(shù)據(jù)邏輯篓跛,用 useState
就顯得力不從心了,需要使用 React 提供的 useReducer
來(lái)處理這類問(wèn)題
useReducer 的用法
import React, {useReducer} from 'react';
console reducer = function(state, action) {
switch (action.type) {
case "increment": // 增加
return {count: state.count+1};
case "decrement": // 減少
return {count: state.count-1};
default
return {count: state.count};
}
};
function Example() {
const [state, dispatch] = useReducer(reducer, {count:0});
const {count} = state;
return (
<div>
<button onClick={ () => dispatch({type: "increment"})}>+</button>
<span>{count}</span>
<button onClick={ () => dispatch({type: "decrement"})}>-</button>
</div>
);
}
export default Example;
- useReducer 接受 reducer 函數(shù)和默認(rèn)值兩個(gè)參數(shù)
- 返回當(dāng)前狀態(tài) state 和 dispatch 函數(shù)的數(shù)組
- 使用方式與
Redux
框架一致 - useReducer 沒(méi)有采用 Redux 方式設(shè)置默認(rèn)值坦刀,是因?yàn)镽eact認(rèn)為狀態(tài)的默認(rèn)值可能來(lái)自于函數(shù)組件的props
function Example({initialState = 0}) {
const {state, dispatch} = useReducer(reducer, { count: initialState} );
...
}
Effct Hook 管理聲明周期
import React, {useState, useEffect} from 'react';
function Example() {
const [count, setCount]= useState(0);
useEffect( () => {
console.log('componentDidMount...');
console.log('componentDidUpdate...');
return() => {
console.log('componentWillUnmount...');
}
}
);
return (
<div>
<button onClick={ () => setCount(count+1)}>
Click me
</button>
</div>
);
}
export default Example;
每次點(diǎn)擊按鈕的時(shí)候愧沟,輸出的內(nèi)容為
componentDidMount...
componentDidUpdate...
componentWillUnmount...
componentDidMount...
componentDidUpdate...
...
- 每次執(zhí)行組件更新時(shí),useEffect 中的回到函數(shù)都會(huì)被調(diào)用鲤遥;
- 在重新繪制前執(zhí)行銷毀操作沐寺,避免造成內(nèi)存泄漏;
- useEffect 可以被視為
componentDidMount, componentDidUpdate, componentWillUnmount
的數(shù)組渴频,并用它關(guān)聯(lián)函數(shù)組件的生命周期 - 類組件的
componentDidMount
,componentDidUpdate
聲明周期函數(shù)都是在 DOM 更新后同步執(zhí)行的 - useEffect 不是同步執(zhí)行的芽丹,不會(huì)阻塞瀏覽器更新界面
- 需要模擬生命周期的同步執(zhí)行,可以使用 React 提供的
useLayoutEffect Hook
6.3 自定義 Hook
- 自定義 Hook:函數(shù)名是以
use
開(kāi)頭的并調(diào)用其他 Hook 的封裝函數(shù) - 自定義 Hook 的每個(gè)狀態(tài)都是獨(dú)立的
使用 axios 實(shí)現(xiàn)一個(gè)自定義 Hook 的案例
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export const useAxios = (url, dependecies) => {
const [isLoading, setIsLoading] = useState(false);
const [response, setReponse] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
axios.get(Url).then((res) => {
setIsLoading(false);
setReponse(res);
}).catch((err) => {
setIsLoading(false);
setError(err);
});
}, dependecies);
return [isLoading, response, error];
};
在 Example 中使用 axois 自定義 Hook 函數(shù)組件
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export const useAxios = (url, dependecies) => {
const [isLoading, setIsLoading] = useState(false);
const [response, setReponse] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
axios.get(Url).then((res) => {
setIsLoading(false);
setReponse(res);
}).catch((err) => {
setIsLoading(false);
setError(err);
});
}, dependecies);
return [isLoading, response, error];
};
function Example() {
let url = 'http://api.douban.com/v2/movie/in_theaters';
const [isLoading, response, error] = useAxios(url, []);
return (
<div>
{isLoading ? <div>Loading...</div> :
(error ? <div> There is an error happned</div> : <div>Success {response}</div>
}
</div>
);
}
export default Example;
自定義 Hook 的優(yōu)勢(shì)
- 簡(jiǎn)潔易讀
- 不會(huì)引起組件嵌套的問(wèn)題
自定義 Hook 使用的注意事項(xiàng)
- 不要在循環(huán)卜朗、條件或嵌套函數(shù)中使用 Hook拔第,并且只能在 React 函數(shù)的頂層使用 Hook,這是因?yàn)?React需要利用調(diào)用順序來(lái)正確更新相應(yīng)的狀態(tài)场钉,以及調(diào)用相應(yīng)的生命周期函數(shù)蚊俺。一旦在循環(huán)或條件分支語(yǔ)句中調(diào)用 Hook,就容易引起調(diào)用順序不一致,產(chǎn)生難以預(yù)料的后果
- 只能在 React 函數(shù)式組件或自定義Hook中使用 Hook逛万。
eslint
避免在開(kāi)發(fā)中引起低級(jí)錯(cuò)誤,可以在項(xiàng)目中安裝一個(gè) eslint 插件
yarn add eslint-plugin-react-hooks --dev
然后在eslint的配置文件中添加如下配置:
{
"plugins" : [
// ...
"react-hooks"
],
"rules" : [
// ...
"react-hooks/rules-of-hooks" : "error",
"react-hooks/exhaustive-deps" : "warn",
]
}
借助 React 提供的 Hook API泳猬,函數(shù)組件可以實(shí)現(xiàn)絕大部分類組件功能,并且 Hook 在共享狀態(tài)邏輯宇植、提高組件復(fù)用性上也有一定的優(yōu)勢(shì)得封。可以預(yù)見(jiàn)的是指郁,Hook將是 React 未來(lái)發(fā)展的重要方向忙上。