本文目錄:
- 1.React入口
- 2.JSX語法
- 3.組件
- 4.正確使用setState
- 5.生命周期
- 6.組件復(fù)合
- 7.redux
- 8.react-redux
- 9.react-router
- 10.PureComponent
- 11.認(rèn)識Hook
- 12.自定義Hook與Hook使用規(guī)則
- 13.Hook API之useMemo與useCallback
參考網(wǎng)址:https://zh-hans.reactjs.org/docs/getting-started.html
1.React入口
起步
- 創(chuàng)建項目: npx create-react-app my-app
- 打開項目: cd my-app
- 啟動項目: npm start
- 暴露配置項: npm run eject
注意:暴露配置項操作是不可逆的
cra文件結(jié)構(gòu)
├── README.md 文檔
├── public 靜態(tài)資源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源碼
├── App.css
├── App.js 根組件
├── App.test.js
├── index.css 全局樣式
├── index.js 入口文文件
├── logo.svg
└── serviceWorker.js pwa支持
├── package.json npm 依賴
入口文件定義置媳,webpack.config.js
entry: [
// WebpackDevServer客戶端肄鸽,它實現(xiàn)開發(fā)時熱更新功能
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// 應(yīng)用程序入口:src/index
paths.appIndexJs,
].filter(Boolean),
webpack.config.js 是webpack配置?文件彻舰,開頭的常量聲明可以看出cra能夠支持ts、sass及css模塊化。
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
React和ReactDom
刪除src下面所有代碼芍碧,新建index.js飞醉。
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>Hello React</h1>, document.querySelector('#root'));
React負(fù)責(zé)邏輯控制芍躏,數(shù)據(jù) -> VDOM
ReactDom渲染實際DOM奠涌,VDOM -> DOM
React使用JSX來描述UI
babel-loader把JSX 編譯成相應(yīng)的 JS 對象宪巨,React.createElement再把這個JS對象構(gòu)造成React需要的虛擬dom。
2.JSX語法
JSX是一種JavaScript的語法擴展溜畅,其格式?比較像模版語言捏卓,但事實上完全是在JavaScript內(nèi)部實現(xiàn)的。
JSX可以很好地描述UI慈格,能夠有效提高開發(fā)效率怠晴,
2.1.基本使用
表達(dá)式{}的使用,index.js
const name = "react study";
const jsx = <div>hello, {name}</div>;
2.2.函數(shù)
函數(shù)也是合法表達(dá)式浴捆,index.js
const obj = {
fistName: "Harry",
lastName: "Potter"
};
function formatName(name) {
return name.fistName + " " + name.lastName;
}
const jsx = <div>{formatName(user)}</div>;
2.3.對象
jsx是js對象蒜田,也是合法表達(dá)式,index.js
const greet = <div>good</div>;
const jsx = <div>{greet}</div>;
2.4.條件語句
條件語句可以基于上?結(jié)論實現(xiàn)选泻,index.js
const show = true;//false;
const greet = <div>good</div>;
const jsx = (
<div>
{/* 條件語句 */}
{show ? greet : "登錄"}
{show && greet}
</div>
);
2.5.數(shù)組
數(shù)組會被作為一組子元素對待冲粤,數(shù)組中存放一組jsx可用于顯示列表數(shù)據(jù)
const a = [0, 1, 2];
const jsx = (
<div>
{/* 數(shù)組 */}
<ul>
{/* diff時候,?首先?比較type页眯,然后是key梯捕,所以同級同類型元素,key值必須得 唯一 */}
{a.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
2.6.屬性的使用
import logo from "./logo.svg";
const jsx = (
<div>
{/* 屬性:靜態(tài)值用雙引號餐茵,動態(tài)值用花括號科阎;class、for等要特殊處理忿族。 */}
<img src={logo} style={{ width: 100 }} className="img" />
</div>
);
2.7.模塊化
css模塊化锣笨,創(chuàng)建index.module.css,index.js
import style from "./index.module.css";
<img className={style.logo} />
或者npm install sass -D
import style from "./index.module.scss";
<img className={style.logo} />
更多css modules規(guī)則參考
http://www.ruanyifeng.com/blog/2016/06/css_modules.html
3.組件
組件道批,從概念上類似于 JavaScript 函數(shù)错英。它接受任意的入?yún)ⅲ?“props”),并返回用于描述頁面展示內(nèi)容的 React 元素隆豹。
組件有兩種形式:class組件和function組件椭岩。
3.1.class組件
class組件通常擁有狀態(tài)和生命周期,繼承于Component璃赡,實現(xiàn)render方法判哥。用class組件創(chuàng)建?個Clock
import React, {
Component
} from "react";
export default class ClassComponent extends React.Component {
constructor(props) {
super(props);
// 使用state屬性維護(hù)狀態(tài),在構(gòu)造函數(shù)中初始化狀態(tài)
this.state = {
date: new Date()
};
}
componentDidMount() {
// 組件掛載之后啟動定時?每秒更新狀態(tài)
this.timerID = setInterval(() => {
// 使用setState?方法更新狀態(tài)
this.setState({
date: new Date()
});
}, 1000);
}
componentWillUnmount() {
// 組件卸載前停止定時?
clearInterval(this.timerID);
}
componentDidUpdate() {
console.log("componentDidUpdate");
}
render() {
return <div > {
this.state.date.toLocaleTimeString()
} < /div>;
}
}
3.2.function組件
函數(shù)組件通常無狀態(tài)碉考,僅關(guān)注內(nèi)容展示塌计,返回渲染結(jié)果即可
從React16.8開始引入了hooks,函數(shù)組件也能夠擁有狀態(tài)侯谁。
用function組件創(chuàng)建一個Clock:
import React, { useState, useEffect } from "react";
export function FunctionComponent(props) {
const [date, setDate] = useState(new Date());
useEffect(() => {//副作用
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);//組件卸載的時候執(zhí)行
}, []);
return (
<div>
<h3>FunctionComponent</h3>
<p>{date.toLocaleTimeString()}</p>
</div>
);
}
提示: 如果你熟悉 React class 的生命周期函數(shù)锌仅,你可以把 useEffect Hook 看做componentDidMount 章钾, componentDidUpdate 和componentWillUnmount 這三個函數(shù)的組合。
4.正確使用setState
setState(partialState, callback)
- partialState : object|function
用于產(chǎn)生與當(dāng)前state合并的子集热芹。 - callback : function
state更新之后被調(diào)用贱傀。
4.1.關(guān)于 setState() 你應(yīng)該了了解三件事:
不要直接修改 State
例如,此代碼不會重新渲染組件:
// 錯誤示范
this.state.comment = 'Hello';
?是應(yīng)該使用 setState() :
// 正確使用
this.setState({comment: 'Hello'});
4.2.State 的更新可能是異步的
出于性能考慮伊脓,React 可能會把多個 setState() 調(diào)用合并成一個調(diào)用府寒。
觀察以下例例子中l(wèi)og的值和button顯示的counter。
import React, {
Component
} from "react";
export default class SetStatePage extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
changeValue = v => {
this.setState({
counter: this.state.counter + v
});
console.log("counter", this.state.counter);
};
setCounter = () => {
this.changeValue(1);
//this.changeValue(2);
//console.log("counter", this.state.counter);
};
render() {
const {
counter
} = this.state;
return (
< div >
<h3 > SetStatePage < /h3>
<button onClick = {this.setCounter} >
{counter}
< /button>
</div>
);
}
}
如果要獲取到最新狀態(tài)值有以下?式:
1.在回調(diào)中獲取狀態(tài)值
changeValue = v => {
this.setState({
counter: this.state.counter + v
},
() => {
console.log("counter", this.state.counter);
}
);
};
- 使用定時?:
setTimeout(() => {
this.setCounter();
}, 0);
- 原生事件中修改狀態(tài)
componentDidMount(){
document.body.addEventListener('click', this.changeValue, false)
}
總結(jié): setState只有在合成事件和生命周期函數(shù)中是異步的丽旅,在原生事件和setTimeout中都是同步的椰棘,這里的異步其實是批量更新纺棺。
4.3.State 的更新會被合并
changeValue = v => {
this.setState({
counter: this.state.counter + v
});
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2);
};
此時的 this.changeValue(1); 不會生效
如果想要鏈?zhǔn)礁聅tate:
changeValue = v => {
this.setState(state => ({
counter: state.counter + v
}));
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2);
};
5.生命周期
參考文檔:https://zh-hans.reactjs.org/docs/react-component.html#constructor
當(dāng)組件實例被創(chuàng)建并插入 DOM 中時榄笙,其生命周期調(diào)用順序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
當(dāng)組件的 props 或 state 發(fā)生變化時會觸發(fā)更新。組件更新的生命周期調(diào)用順序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapsBeforeUpdate()
- componentDidUpdate()
以下的三個生命周期函數(shù)將被廢棄祷蝌,用getDerivedStateFromProps代替茅撞,目前使用的話加上UNSAFE_:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
引入兩個新的生命周期函數(shù)
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps 會在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時都會被調(diào)用巨朦。它應(yīng)返回一個對象來更新 state米丘,如果返回 null 則不更新任何內(nèi)容。
請注意糊啡,不管原因是什么拄查,都會在每次渲染前觸發(fā)此方法。這與UNSAFE_componentWillReceiveProps 形成對比棚蓄,后者僅在父組件重新渲染時觸發(fā)堕扶,而不是在內(nèi)部調(diào)用 setState 時。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
在render之后梭依,在componentDidUpdate之前稍算。
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節(jié)點)之前調(diào)用。它使得組件能在發(fā)生更改之前從 DOM 中捕獲一些信息(例如役拴,滾動位置)糊探。此生命周期的任何返回值將作為參數(shù)傳遞給 componentDidUpdate(prevProps, prevState, snapshot) 。
6.組件復(fù)合
復(fù)合組件給與你?夠的敏捷去定義?定義組件的外觀和行為河闰,這種?式更明確和安全科平。如果組件間有公?的非UI邏輯,將它們抽取為JS模塊導(dǎo)入使用而不是繼承它姜性。
不具名
新建一個layout.js文件
import React, {
Component
} from "react";
import TopBar from "../components/TopBar";
import BottomBar from "../components/BottomBar";
export default class Layout extends Component {
componentDidMount() {
const {
title = "商城"
} = this.props;
document.title = title;
}
render() {
const {
children,
showTopBar,
showBottomBar
} = this.props;
console.log("children", children);
return (
< div >
{showTopBar && <TopBar />}
{children}
{showBottomBar && <BottomBar />}
</div>
);
}
}
UserPage.js:
import React, { Component } from "react";
import Layout from "./Layout";
export default class UserPage extends Component {
render() {
return (
<Layout showTopBar={true} showBottomBar={true} title="用戶中心">
<div>
<h3>UserPage</h3>
</div>
</Layout>
);
}
}
具名
類似于Vu中的具名插槽瞪慧,我們傳一個對象進(jìn)去
import React, {
Component
} from "react";
import Layout from "./Layout";
export default class HomePage extends Component {
render() {
return ( < Layout showTopBar = {
false
}
showBottomBar = {
true
}
title = "商城首頁" > {
/* <div>
<h3>HomePage</h3>
</div> */
} {
{
content: (
<div >
<h3 > HomePage < /h3>
</div>
),
txt: "這是個?文本",
btnClick: () => {
console.log("btnClick");
}
}
} <
/Layout>
);
}
}
7.redux
在下面的場景中,引入 Redux 是比較明智的
- 你有著相當(dāng)?量的污抬、隨時間變化的數(shù)據(jù)汞贸;
- 你的 state 需要有一個單一可靠數(shù)據(jù)來源绳军;
- 你覺得把所有 state 放在最頂層組件中已經(jīng)?法滿足需要了了。
- 某個組件的狀態(tài)需要共享矢腻。
redux 是 JavaScript應(yīng)用的狀態(tài)容?门驾,提供可預(yù)測化的狀態(tài)管理。它保證程序行為一致性且易于測試多柑。
安裝redux
npm install redux --save
redux上手
用一個累加?舉例
- 需要一個store來存儲數(shù)據(jù)
- store里的reducer初始化state并定義state修改規(guī)則
- 通過dispatch一個action來提交對數(shù)據(jù)的修改
- action提交到reducer函數(shù)里奶是,根據(jù)傳入的action的type,返回新的state
創(chuàng)建store竣灌,src/store/ReduxStore.js
import {
createStore
} from 'redux'
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
const store = createStore(counterReducer)
export default store
創(chuàng)建ReduxPage
import React, {
Component
} from "react";
import store from "../store/ReduxStore";
export default class ReduxPage extends Component {
componentDidMount() {
store.subscribe(() => {
console.log("subscribe");
this.forceUpdate();
//this.setState({});
});
}
add = () => {
store.dispatch({
type: "ADD"
});
};
minus = () => {
store.dispatch({
type: "MINUS"
});
};
render() {
console.log("store", store);
return (
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
<button onClick={this.add}>add</button>
<button onClick={this.minus}>minus</button>
</div>
);
}
}
如果點擊按鈕不能更新聂沙,因為沒有訂閱(subscribe)狀態(tài)變更
還可以在src/index.js的render里訂閱狀態(tài)變更
import store from './store/ReduxStore'
const render = () => {
ReactDom.render( <
App / > ,
document.querySelector('#root')
)
}
render()
store.subscribe(render)
檢查點
- createStore 創(chuàng)建store
- reducer 初始化、修改狀態(tài)函數(shù)
- getState 獲取狀態(tài)值
- dispatch 提交更新
- subscribe 變更訂閱
8.react-redux
安裝
npm install react-redux --save
react-redux提供了兩個api
- Provider 為后代組件提供store
- connect 為組件提供數(shù)據(jù)和變更方法
全局提供store初嘹,index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store/'
import { Provider } from 'react-redux'
ReactDom.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector('#root')
)
獲取狀態(tài)數(shù)據(jù)及汉,ReactReduxPage.js
import React, {
Component
} from "react";
import {
connect
} from "react-redux";
class ReactReduxPage extends Component {
render() {
const {
num,
add,
minus
} = this.props;
return ( <
div >
<
h1 > ReactReduxPage < /h1> <
p > {
num
} < /p> <
button onClick = {
add
} > add < /button> <
button onClick = {
minus
} > minus < /button> < /
div >
);
}
}
const mapStateToProps = state => {
return {
num: state,
};
};
const mapDispatchToProps = {
add: () => {
return {
type: "add"
};
},
minus: () => {
return {
type: "minus"
};
}
};
export default connect(
mapStateToProps, //狀態(tài)映射 mapStateToProps
mapDispatchToProps, //派發(fā)事件映射
)(ReactReduxPage);
connect中的參數(shù):state映射和事件映射
9.react-router
react-router包含3個庫,react-router屯烦、react-router-dom和react-router-native坷随。react-router提供最基本的路由功能,實際使用的時候我們不會直接安裝react-router驻龟,而是根據(jù)應(yīng)用運行的環(huán)境選擇安裝react-router-dom(在瀏覽?中使用)或react-router-native(在rn中使用)温眉。react-router-dom和react-router-native都依賴react-router,所以在安裝時翁狐,react-router也會?自動安裝类溢,創(chuàng)建web應(yīng)用,
使用:
安裝
npm install --save react-router-dom
基本使用
react-router中奉行一切皆組件的思想露懒,路由?-Router闯冷、鏈接-Link、路由-Route隐锭、獨占-Switch窃躲、重定向-Redirect都以組件形式存在
創(chuàng)建RouterPage.js
import React, {
Component
} from "react";
import {
BrowserRouter as Router,
Route,
Link
} from "react-router-dom";
export default class RouterPage extends Component {
render() {
return (
<div >
<h3 > RouterPage < /h3>
<Router >
<Link to = "/" > 首頁 < /Link>
<Link to = "/user" > 用戶中? < /Link>
{/* 根路路由要添加exact,實現(xiàn)精確匹配 */ }
<Route
exact
path = "/"
component = {HomePage}
//children={() => <div>children</div>}
//render={() => <div>render</div>}
/>
<Route
path = "/user"
component = {UserPage}
/>
</Router>
</div>
);
}
}
class HomePage extends Component {
render() {
return ( <
div >
<
h3 > HomePage < /h3> <
/div>
);
}
}
class UserPage extends Component {
render() {
return ( <
div >
<
h3 > UserPage < /h3> <
/div>
);
}
}
Route渲染內(nèi)容的三種方式
Route渲染優(yōu)先級:children>component>render钦睡。
這三種方式互斥蒂窒,你只能用一種。
children:func
有時候荞怒,不管location是否匹配洒琢,你都需要渲染一些內(nèi)容,這時候你可以用children褐桌。
除了不管location是否匹配都會被渲染之外衰抑,其它?作?法與render完全一樣
render:func
但是當(dāng)你用render的時候,你調(diào)用的只是個函數(shù)荧嵌。
只在當(dāng)location匹配的時候渲染呛踊。
component: component
只在當(dāng)location匹配的時候渲染砾淌。
404頁面
設(shè)定一個沒有path的路由在路由列表最后面,表示一定匹配
{/* 添加Switch表示僅匹配一個*/ }
<Switch >
{/* 根路由要添加exact谭网,實現(xiàn)精確匹配 */ }
<Route
exact
path = "/"
component = {HomePage}
/>
<Route
path = "/user"
component = {UserPage}
/>
<Route
component = {EmptyPage}
/>
</Switch>
class EmptyPage extends Component {
render() {
return (
<div >
<h3 > EmptyPage - 404 < /h3>
</div>
);
}
}
10.PureComponent
React.PureComponent與 React.Component很相似汪厨。兩者的區(qū)別在于 React.Component并未實現(xiàn) shouldComponentUpdate(),而 React.PureComponent 中以淺層對比 prop 和 state 的方式來實現(xiàn)了該函數(shù)愉择。
如果賦予 React 組件相同的 props 和 state劫乱,render() 函數(shù)會渲染相同的內(nèi)容,那么在某些情況下使用 React.PureComponent 可提高性能锥涕。
實現(xiàn)性能優(yōu)化
定制了shouldComponentUpdate后的Component
import React, {Component,PureComponent} from "react";
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
// obj: {
// num: 2,
// },
};
}
setCounter = () => {
this.setState({
counter: 100,
// obj: {
// num: 200,
// },
});
};
render() {
const {
counter,
obj
} = this.state;
console.log("render");
return (
<div >
<h1 > PuerComponentPage < /h1>
<div onClick = {this.setCounter} > counter: {counter} < /div>
</div>
);
}
}
11.認(rèn)識Hook
Hook 是什么衷戈? Hook 是一個特殊的函數(shù),它可以讓你“鉤入” React 的特性层坠。例如殖妇, useState 是允許你在 React 函數(shù)組件中添加 state 的 Hook。
什么時候會用 Hook窿春? 如果你在編寫函數(shù)組件并意識到需要向其添加一些 state拉一,以前的做法是必須將其它轉(zhuǎn)化為 class。現(xiàn)在你可以在現(xiàn)有的函數(shù)組件中使用 Hook旧乞。
import React, { useState } from "react";
export default function HookPage(props) {
// 聲明一個叫 “count” 的 state 變量,初始化為0
const [count, setCount] = useState(0);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
使用 Effect Hook
Effect Hook 可以讓你在函數(shù)組件中執(zhí)行副作用操作磅氨。
數(shù)據(jù)獲取尺栖,設(shè)置訂閱以及手動更改 React 組件中的 DOM 都屬于副作用。不管你知不知道這些操作烦租,或是“副作用”這個名字延赌,應(yīng)該都在組件中使用過它們。
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 聲明一個叫 “count” 的 state 變量叉橱,初始化為0
const [count, setCount] = useState(0);
// 與 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更新 title
document.title = `You clicked ${count} times`;
});
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
在函數(shù)組件主體內(nèi)(這里指在 React 渲染階段)改變 DOM挫以、添加訂閱、設(shè)置定時?窃祝、記錄?志以及執(zhí)行其他包含副作?的操作都是不被允許的掐松,因為這可能會產(chǎn)生莫名其妙的 bug 并破壞 UI 的一致性。
使用 useEffect 完成副作用操作粪小。賦值給 useEffect 的函數(shù)會在組件渲染到屏幕之后執(zhí)行大磺。你可以把 effect 看作從 React 的純函數(shù)式世界通往命令式世界的逃生通道。
默認(rèn)情況下探膊,effect 將在每輪渲染結(jié)束后執(zhí)行杠愧,但你可以選擇讓它 在只有某些值改變的時候 才執(zhí)行。
effect 的條件執(zhí)行
默認(rèn)情況下逞壁,effect 會在每輪組件渲染完成后執(zhí)行流济。這樣的話锐锣,一旦 effect 的依賴發(fā)生變化,它就會被重新創(chuàng)建绳瘟。
然而刺下,在某些場景下這么做可能會矯枉過正。比如稽荧,在上一部分的訂閱示例中橘茉,我們不需要在每次組件更新時都創(chuàng)建新的訂閱,而是僅需要在 source props 改變時重新創(chuàng)建姨丈。
要實現(xiàn)這一點畅卓,可以給 useEffect 傳遞第二個參數(shù),它是 effect 所依賴的值數(shù)組蟋恬。更新后的示例如下:
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 聲明一個叫 “count” 的 state 變量翁潘,初始化為0
const [count, setCount] = useState(0);
const [date, setDate] = useState(new Date());
// 與 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更新 title
document.title = `You clicked ${count} times`;
}, [count]);
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
}, []);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{date.toLocaleTimeString()}</p>
</div>
);
}
此時,只有當(dāng) useEffect第二個參數(shù)組里的數(shù)值改變后才會重新創(chuàng)建訂閱歼争。
清除 effect
通常拜马,組件卸載時需要清除 effect 創(chuàng)建的諸如訂閱或計時? ID 等資源。要實現(xiàn)這一點沐绒,useEffect函數(shù)需返回一個清除函數(shù)俩莽,以防止內(nèi)存泄漏,清除函數(shù)會在組件卸載前執(zhí)行乔遮。
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
12.自定義Hook與Hook使用規(guī)則
有時候我們會想要在組件之間重用一些狀態(tài)邏輯扮超。目前為止,有兩種主流方案來解決這個問題:高階組件和 render props蹋肮。自定義 Hook 可以讓你在不增加組件的情況下達(dá)到同樣的目的出刷。
自定義 Hook 是一個函數(shù),其名稱以 “use” 開頭坯辩,函數(shù)內(nèi)部可以調(diào)用其他的 Hook馁龟。
import React, { useState, useEffect, useMemo } from "react";
export default function CustomHookPage(props) {
//定義一個叫count的state變量,初始化為0
const [count, setCount] = useState(0);
//和didMount漆魔、didUpdate類似
useEffect(() => {
console.log("count effect");
// 只需要在count發(fā)生改變的時候執(zhí)行就可以啦
document.title = `點擊了了${count}次`;
}, [count]);
return (
<div>
<h3>自定義Hook</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{useClock().toLocaleTimeString()}</p>
</div>
);
}
//自定義hook坷檩,命名必須以use開頭
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
console.log("date effect");
//只需要在didMount時候執(zhí)行就可以了
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
//清除定時?,類似willUnmount
return () => clearInterval(timer);
}, []);
return date;
}
Hook 使用規(guī)則
Hook 就是 JavaScript 函數(shù)有送,但是使用它們會有兩個額外的規(guī)則:
只能在函數(shù)最外層調(diào)用 Hook淌喻。不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用雀摘。
只能在 React 的函數(shù)組件中調(diào)用 Hook裸删。不要在其他 JavaScript 函數(shù)中調(diào)用。(還有?個地?方可以調(diào)用 Hook —— 就是自定義的 Hook 中阵赠。)
13.Hook API之useMemo與useCallback
useMemo
把“創(chuàng)建”函數(shù)和依賴項數(shù)組作為參數(shù)傳入 useMemo 涯塔,它僅會在某個依賴項改變時才重新計算memoized 值肌稻。這種優(yōu)化有助于避免在每次渲染時都進(jìn)行高開銷的計算。
···
import React, { useState, useMemo } from "react";
export default function UseMemoPage(props) {
const [count, setCount] = useState(0);
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
//只有count變化匕荸,這?才重新執(zhí)?
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseMemoPage</h3>
<p>expensive:{expensive}</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
</div>
);
}
···
useCallback
把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項數(shù)組作為參數(shù)傳入 useCallback 爹谭,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個依賴項改變時才會更新榛搔。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate )的子組件時诺凡,它將?常有用。
···
import React, { useState, useCallback, PureComponent } from "react";
export default function UseCallbackPage(props) {
const [count, setCount] = useState(0);
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
<Child addClick={addClick} />
</div>
);
}
class Child extends PureComponent {
render() {
console.log("child render");
const { addClick } = this.props;
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
);
}
}
···
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps) 践惑。