生命周期與鉤子函數(shù)(重點(diǎn))
生命周期指的react實(shí)例及組件從創(chuàng)建到運(yùn)行到銷毀的完整的過(guò)程。
組件的生命周期可分成三個(gè)階段(狀態(tài)):
- Mounting:創(chuàng)建階段:已插入真實(shí) DOM
- Updating:運(yùn)行階段:正在被重新渲染
- Unmounting:銷毀階段:已移出真實(shí) DOM
鉤子函數(shù)指提前埋在生命周期中的函數(shù),等到程序運(yùn)行到這一刻時(shí),它會(huì)自動(dòng)執(zhí)行。
常用的鉤子函數(shù)
componentWillMount、componentWillReceiveProps、shouldComponentUpdate、ComponentDidMount
代碼示例:
父組件代碼
class Comp1 extends React.Component{
constructor(){
super();
this.state = {
a:1,
isShow:true
}
}
up_click(){
this.setState(state=>({
a:state.a+1
}));
}
un_click(){
this.setState(state=>({
isShow:!state.isShow
}));
}
render(){
return (<div>
<input type="button" onClick={()=>{this.up_click()}} value="更改組件2的屬性" />
<input type="button" onClick={()=>{this.un_click()}} value="卸載組件2" />
{this.state.isShow ? <Comp2 b={this.state.a} /> : null}
</div>);
}
}
ReactDOM.render( <Comp1 />, document.getElementById('root'));
子組件代碼
class Comp2 extends React.Component{
// 創(chuàng)建階段
constructor(){
super();
console.log("constructor");
}
componentWillMount(){
console.log("componentWillMount在渲染前調(diào)用");
}
render(){
console.log("render");
return <div id='div2'>comp2-{this.props.b}</div>
}
componentDidMount(){
console.log("componentDidMount在第一次渲染后調(diào)用");
}
// 運(yùn)行中階段
componentWillReceiveProps(newProps) {
console.log(`newProps: ${newProps}
在組件接收到一個(gè)新的 prop (更新后)時(shí)被調(diào)用。這個(gè)方法在初始化render時(shí)不會(huì)被調(diào)用。`)
}
shouldComponentUpdate(newProps, newState) {
console.log(`newProps: ${newProps} newState: ${newState}
返回一個(gè)布爾值。在組件接收到新的props或者state時(shí)被調(diào)用。
在初始化時(shí)或者使用forceUpdate時(shí)不被調(diào)用??梢栽谀愦_認(rèn)不需要更新組件時(shí)使用。`)
return true; // true表示更新組件;false表示不更新組件
}
componentWillUpdate(nextProps, nextState) {
console.log(`nextProps: ${nextProps} nextState:${nextState}
在組件接收到新的props或者state但還沒(méi)有render時(shí)被調(diào)用。在初始化時(shí)不會(huì)被調(diào)用。`);
}
componentDidUpdate(prevProps, prevState) {
console.log(`prevProps:${prevProps} prevState:${prevState}
在組件完成更新后立即調(diào)用。在初始化時(shí)不會(huì)被調(diào)用。`)
}
// 銷毀階段
componentWillUnmount() {
console.log('在組件從 DOM 中移除的時(shí)候立刻被調(diào)用')
}
}
新增的生命周期鉤子函數(shù)
在 react v16.3 時(shí),新引入了新的生命周期函數(shù):getDerivedStateFromProps,getSnapshotBeforeUpdate。
在未來(lái)的 react v17 時(shí),componentWillMount、componentWillReceiveProps、componentWillUpdate 要被廢棄。
getDerivedStateFromProps
// 父組件的state發(fā)生變化,導(dǎo)致父組件中的當(dāng)前組件被重新渲染,當(dāng)前組件的props被修改時(shí),該鉤子函數(shù)會(huì)被觸發(fā)。
/*componentWillReceiveProps(nextProps){
console.log('nextProps: ', nextProps);
}*/
// 不僅僅具有componentWillReceiveProps的能力,自身組件state變化時(shí),該鉤子函數(shù)也會(huì)被觸發(fā)。
// 該函數(shù)在shouldComponentUpdate之前執(zhí)行。
// static描述是靜態(tài)函數(shù),其沒(méi)有this指向,所以無(wú)權(quán)操作實(shí)例,所以更安全,而且消耗性能低。
// nextProps 傳入后的prop數(shù)據(jù),即最新的props
// prevState 相對(duì)于合并的state來(lái)說(shuō)的前一個(gè)狀態(tài)
static getDerivedStateFromProps(nextProps, prevState) {
console.log('nextProps: ', nextProps);
console.log('prevState: ', prevState);
return {x:nextProps.a} // 合并到當(dāng)前組件的state
//return null
}
配合 componentDidUpdate 周期函數(shù),getDerivedStateFromProps 是為了替代 componentWillReceiveProps 而出現(xiàn)的。它將原本 componentWillReceiveProps 功能進(jìn)行劃分 —— 更新 state 和 操作/調(diào)用 props,很大程度避免了職責(zé)不清而導(dǎo)致過(guò)多的渲染, 從而影響應(yīng)該性能。
getSnapshotBeforeUpdate
在 render 之后執(zhí)行的鉤子
// 更新之前
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate')
console.log('prevProps:', prevProps)
console.log('prevState:', prevState)
return {x:1}
}
// 更新之后 snapshot能夠得到 getSnapshotBeforeUpdate 的返回值
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate')
console.log('prevProps:', prevProps)
console.log('prevState:', prevState)
console.log('snapshot:', snapshot)
}
性能優(yōu)化 shouldComponentUpdate()
決定視圖是否需要重新渲染
改變a時(shí),render重新執(zhí)行;改變b時(shí),render不會(huì)重新執(zhí)行
class App extends Component {
constructor(){
super();
this.state = {
a : 1,
b : 1
}
this.fna = ()=>{
this.setState({ a: new Date().getTime() })
}
this.fnb = function(){
this.setState({ b: new Date().getTime() })
}
}
shouldComponentUpdate(nextProps, nextState){
if( this.state.a !== nextState.a ){
return true;
}else{
return false;
}
}
render(){
return <div>
a: {this.state.a}<br />
b: {this.state.b}<br />
<input type="button" value="改變a" onClick={this.fna} />
<input type="button" value="改變b" onClick={()=>this.fnb()} />
</div>;
}
}
純組件 PureComponent(淺比較)
pure 是純的意思,PureComponent 也就是純組件
淺比較,如果是PureComponent,那么執(zhí)行add時(shí),視圖不會(huì)更新;
在修改純組件中的狀態(tài)時(shí),檢查更新前后的狀態(tài)是否一致(棧中比較),如果一致,則不更新視圖,如果不一致,才更新視圖。
而如果是React.Component,那么當(dāng)執(zhí)行add時(shí),視圖會(huì)自動(dòng)更新。
比較修改狀態(tài)前后是否一致時(shí),在堆中比較。
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
a : 1,
arr : ['a','b','c']
}
}
updata(){
this.setState({
a: this.state.a+1
})
}
add(){
this.setState(state=>{
state.arr.push( new Date().toLocaleString() );
return state;
})
}
render() {
return (
<ul>
<li>
<input
type="button"
value={'修改:'+this.state.a}
onClick={this.updata.bind(this)} />
<input
type="button"
value="添加"
onClick={this.add.bind(this)} />
</li>
{ this.state.arr.map((item, ind)=><li key={ind}>{item}</li>) }
</ul>
);
}
}
使用 PureComponent 可能導(dǎo)致不自動(dòng)更新頁(yè)面
因?yàn)镻ureComponent是淺比較,所以對(duì)數(shù)組和對(duì)象的更新,如果只是改變了堆中數(shù)據(jù),那么系統(tǒng)是不會(huì)自動(dòng)觸發(fā)render函數(shù)的,就不會(huì)自動(dòng)更新了,這個(gè)過(guò)程在react中被稱為數(shù)據(jù)的突變。
把PureComponent換成Component就不會(huì)出現(xiàn)這個(gè)問(wèn)題了,但Component屬于深比較,性能消耗的多一些。
不會(huì)突變的數(shù)據(jù)力量
PureComponent淺比較中如何觸發(fā)render?
只要改變了棧中的數(shù)據(jù),視圖層就會(huì)自動(dòng)更新。
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
Object.assign({}, colormap, {right: 'blue'});
以上代碼都可以在PureComponent組件中,觸發(fā)render。
父組件向子組件傳遞數(shù)據(jù)(重點(diǎn))
react 和 vue 和 angular 一樣,都是單項(xiàng)數(shù)據(jù)流,所以項(xiàng)目中推薦的是父向子傳遞數(shù)據(jù)。
狀態(tài)提升
非父子組件數(shù)據(jù)通信時(shí),把一些共有的數(shù)據(jù)放到共有的節(jié)點(diǎn)中。
爺爺、大伯、父親、孩子
孩子:修改大伯的狀態(tài)時(shí),應(yīng)該把大伯的狀態(tài)提升到爺爺上,然后由爺爺以屬性的形式,把方法先傳給父親,然后父親以屬性的形式把方法傳給孩子,孩子觸發(fā)該方法,就能觸發(fā)爺爺上的方法,爺爺修改了狀態(tài),重新傳給大伯,大伯重新渲染頁(yè)面。
父組件在屬性上描述想要傳入的數(shù)據(jù)即可
<abc xyz="123"></abc>
子組件使用 props 接收傳入的數(shù)據(jù)即可
this.props.xyz
子組件向父組件傳遞數(shù)據(jù)
父組件:
fn(a, b){
alert(a+b)
}
render(){
return ( <div>
父組件 <br/>
<abc fn={this.fn.bind(this)}></abc>
</div> )
}
子組件:
<button onClick={()=>{ this.props.fn(1,2) }} >按鈕</button>
EventBus 中央事件總線
非父子組件,數(shù)據(jù)通信,eventbus。
bus.js
import { Component } from 'react'
import { EventEmitter } from 'events'
const bus = new EventEmitter();
Component.prototype.$bus = bus;
index.js
import './modules/bus.js'
創(chuàng)建自定義事件
this.$bus.on('abc', function(){})
觸發(fā)自定義事件
this.$bus.emit('abc')
emitter對(duì)象下還有once、off等方法
Context 狀態(tài)樹
context 狀態(tài)樹雖然沒(méi)有被廢除,但官方是不建議使用的。
解決的是復(fù)雜組件關(guān)系時(shí),數(shù)據(jù)共享的問(wèn)題,官方建議用eventbus或redux來(lái)解決。
Provider 提供者;Consumer 消費(fèi)者
// 創(chuàng)建名字叫做colorContext的狀態(tài)樹上下文對(duì)象,默認(rèn)值為red。
const colorContext = React.createContext('red');
// 創(chuàng)建外層組件(Provider提供了一些數(shù)據(jù)共享的能力,表示colorContext這顆狀態(tài)樹對(duì)象的值設(shè)置為yellow)
// 即,當(dāng)前組件的后代組件,都可以通過(guò)Consumer來(lái)使用共享中的數(shù)據(jù),即yellow這個(gè)數(shù)據(jù)。
class Container extends Component {
render(){
return <colorContext.Provider value='yellow'>
<div> Container
<Temp2></Temp2>
</div>
</colorContext.Provider>
}
}
// 創(chuàng)建中間層組件(復(fù)雜的組件關(guān)系時(shí),這可能是很多層,如果使用context,就不需要一層一層的傳遞props了)
class Temp2 extends Component {
render(){
return <div> temp
<Box></Box>
</div>
}
}
// 創(chuàng)建內(nèi)層組件(在這層組件中,使用前面提供的數(shù)據(jù))
class Box extends Component {
render(){
return <colorContext.Consumer>
{c=><div> box
<div style={{background:c}}>{c}</div>
</div>}
</colorContext.Consumer>
}
}
把 Container 類中的 colorContext 這個(gè)標(biāo)簽去掉,直接在 Box 類中用 colorContext 就能夠看到默認(rèn)值了,注意return 后面不能有換行。
HOC 高階組件
高階組件(HOC)是react中的高級(jí)技術(shù),用來(lái)重用組件邏輯。
高階組件就是一個(gè)函數(shù),且該函數(shù)接受一個(gè)組件作為參數(shù),并返回一個(gè)新的組件。
// 原始組件
class OldComp extends Component {
render(){
return <div>old</div>
}
}
// 高階組件
function higherOrderComponent(Comp){
return class extends React.Component {
render(){
return <Comp />;
}
}
}
// 新組件
const NewComp = higherOrderComponent(OldComp);
// App組件的渲染
class App extends Component {
render(){
return <NewComp />;
}
}
Slot 插槽
基本
組合---包含關(guān)系---slot
class Component1 extends Component {
render(){
return <div>
{ this.props.children }
</div>;
}
}
class App extends Component {
constructor(){
super();
this.state = {}
}
render(){
return (
<Component1>
<h1>標(biāo)題</h1>
<p>內(nèi)容</p>
</Component1>
);
}
}
多個(gè)
class Component1 extends Component {
render(){
return <div>
{ this.props.left }
{ this.props.right }
{ this.props.children }
</div>;
}
}
class App extends Component {
constructor(){
super();
this.state = {}
}
render(){
return (
<Component1
left={<div>你好</div>}
right={<div>hello</div>}
>
children僅解析這內(nèi)容,不會(huì)取left和right
</Component1>
);
}
}
Flux 狀態(tài)管理
Flux 是比較舊的一種狀態(tài)管理技術(shù),現(xiàn)在 react 官方已經(jīng)不推薦使用了。
網(wǎng)上看到的 flux 教程幾乎都是2015-2017年的,而當(dāng)時(shí)的 react 和現(xiàn)在的 react 的代碼寫法上有很大區(qū)別。
下面是新代碼的寫法。
App.js 組件視圖層入口
用戶打開瀏覽器后看到的頁(yè)面,這個(gè)頁(yè)面有2部分功能。
- 從 store 中獲取數(shù)據(jù),渲染到當(dāng)前組件的視圖層上。
- 點(diǎn)擊按鈕,觸發(fā) action 中的方法,這個(gè)方法改變 store 中的數(shù)據(jù)。(store數(shù)據(jù)改變后,當(dāng)前 App 組件重新渲染)
import React, { Component } from 'react';
// 所有的動(dòng)作
import Actions from './store/actions';
// 倉(cāng)庫(kù),這里保存的是數(shù)據(jù)和操作數(shù)據(jù)的方法
import store from './store/store';
// 組件
class App extends Component {
constructor(){
super();
this.state = {
todos : store.todos
}
}
// 執(zhí)行 action 中的方法
add(){
Actions.add('你好');
}
// 注冊(cè)一個(gè)回調(diào)函數(shù),因?yàn)?flux 中的數(shù)據(jù)修改后,不會(huì)自動(dòng)更新視圖,
// 所以向 store 中注冊(cè)一個(gè)函數(shù),
// 等 store 中數(shù)據(jù)發(fā)生變化后,要調(diào)用這個(gè)回調(diào)函數(shù),進(jìn)而修改當(dāng)前視圖。
componentDidMount(){
store.change(()=>{
this.setState({
todos : store.todos
})
})
}
render() {
return (
<div>
<button onClick={()=>{this.add()}}>添加</button>
{ this.state.todos.map(item=><li key={item.id}>
{item.text}
</li>) }
</div>
);
}
}
export default App;
action.js 動(dòng)作
動(dòng)作頁(yè)面,所有操作 flux 的動(dòng)作都寫在此處,比如對(duì) store 中某數(shù)據(jù)的增刪改查操作的動(dòng)作。
實(shí)際上是調(diào)用 dispatcher 的 dispatch 方法。
import appDispatcher from './dispatcher';
export default {
add( val ){
appDispatcher.dispatch({type:'ADD', val});
}
}
dispatcher.js 派發(fā)
只做一些派發(fā),業(yè)務(wù)邏輯都寫在 store 中
// npm i flux or yarn add flux
import { Dispatcher } from 'flux';
import store from './store';
// 創(chuàng)建 dispatcher
const appDispatcher = new Dispatcher();
// 當(dāng)用戶執(zhí)行appDispatcher.dispatch時(shí),實(shí)際上執(zhí)行的就是下面注冊(cè)進(jìn)來(lái)的函數(shù)
appDispatcher.register(action => {
// console.log('dispatcher -> action:', action);
switch( action.type ){
case 'ADD':
store.addTodo( action.val );
store.emit('change'); // 觸發(fā)用戶提交過(guò)來(lái)的回調(diào)函數(shù)
break;
}
})
export default appDispatcher;
store.js 倉(cāng)庫(kù)
倉(cāng)庫(kù),存儲(chǔ)數(shù)據(jù)的容器。
import { EventEmitter } from 'events';
// 創(chuàng)建store對(duì)象,讓store對(duì)象具有on和emit方法(Object.assign是將對(duì)象進(jìn)行合并)
const store = Object.assign({}, EventEmitter.prototype, {
todos : [],
addTodo( val ){
this.todos.push({text:val, id:new Date().getTime()})
},
change( callback ){
this.on('change', callback);
}
});
export default store;
Yarn
yarn 和 npm 一樣,都是包管理工具,解決的都是在項(xiàng)目中,對(duì)文件的上傳、下載、依賴描述等等相關(guān)問(wèn)題。
| 作用 | npm | Yarn |
|---|---|---|
| 安裝 | npm install(i) | yarn |
| 卸載 | npm uninstall(un) | yarn remove |
| 全局安裝 | npm install xxx –-global(-g) | yarn global add xxx |
| 安裝包 | npm install xxx –save(-S) | yarn add xxx |
| 開發(fā)模式安裝包 | npm install xxx –save-dev(-D) | yarn add xxx –dev(-D) |
| 更新 | npm update –save | yarn upgrade |
| 全局更新 | npm update –global | yarn global upgrade |
| 卸載 | npm uninstall [–save/–save-dev] | yarn remove xx |
| 清除緩存 | npm cache clean | yarn cache clean |
| 重裝 | rm -rf node_modules && npm install | yarn upgrade |
