來(lái)源掘金小冊(cè) - react實(shí)戰(zhàn):設(shè)計(jì)模式和最佳實(shí)踐
react設(shè)計(jì)思想
- 屬性展開(kāi) - 保留當(dāng)前組件需要的props,并且使其他的props傳遞下去
var obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
const { a, ...other } = obj
console.log(other) // {b: 2, c: 3, d: 4, e: 5}
- 在react中,界面完全由數(shù)據(jù)驅(qū)動(dòng)
- 在react中,一切都是組件
- props是react組件之間通訊的基本方式
// 可以實(shí)現(xiàn)記錄用戶(hù)訪問(wèn)路徑操作信息,每次進(jìn)入某個(gè)頁(yè)面或者功能的時(shí)候都會(huì)訪問(wèn)一個(gè)線上資源
// a.js
export default Beacon extends React.Component {
componentDidMount() {
const beacon = new Image()
beacon.src = 'https://age/i.png'
}
render() {
return null
}
}
// b.js
render() {
<div>
<Beacon/>
</div>
}
// 代碼組織方式優(yōu)化 - renderXXX函數(shù)訪問(wèn)的是同樣的props和state,代碼耦合嚴(yán)重
class StopWatch extends React.Component {
renderClock() {
}
renderButtons() {
}
renderSplitTimes() {
}
render() {
const Clock = this.renderClock()
const Buttons = this.renderButtons()
const SplitTimes = this.renderSplitTimes()
return (
<div>
{Clock}
{Buttons}
{SplitTimes}
</div>
)
}
}
// 更好的組織方式
class StopWatch extends React.Component {
render() {
return (
<div>
<Clock />
<Buttons />
<SplitTimes />
</div>
)
}
}
const Clock = (props) = {
}
const Buttons = (props) => {
}
const SplitTimes = (props) => {
}
設(shè)計(jì)react組件原則及最佳實(shí)踐
1. 保持接口小,props數(shù)量要少
2. 根據(jù)數(shù)據(jù)邊界來(lái)劃分組件,充分利用組合
3. 把state往上層組件提取,讓下層組件只需要實(shí)現(xiàn)為純函數(shù)
4. 避免renderXXXX函數(shù)
5. 給回調(diào)函數(shù)類(lèi)型的props加統(tǒng)一前綴,比如on or handle - onStart
6. 使用propTypes來(lái)定義組件的props
7. 盡量每個(gè)組件都有自己專(zhuān)屬的文件
8. 用解構(gòu)賦值的方式獲取參數(shù)props的屬性 - const { a, b } = { a: 1, b: 2 }
9. 利用屬性初始化來(lái)定義state和成員函數(shù) - 給默認(rèn)值 const { data = [] } = this.props
/*
1. 盡量不要在jsx中寫(xiě)內(nèi)聯(lián)函數(shù)
2. 每次渲染,都會(huì)產(chǎn)生新的函數(shù)對(duì)象
3. 每次傳給組件的props(方法,數(shù)據(jù),狀態(tài)等)都是新的,無(wú)法通過(guò)shouldComponentUpdate對(duì)props的檢查來(lái)避免重復(fù)渲染
*/
<Buttons
activated={this.state.isStarted}
onStart={() => {/* TODO */}}
onReset={() => {/* TODO */}}
/>
/*
1. style屬性的使用
2. 內(nèi)聯(lián)樣式在組件每次渲染時(shí)都會(huì)創(chuàng)建一個(gè)新的style對(duì)象
3. 如果樣式不發(fā)生改變,應(yīng)該把style對(duì)象提取到組件之外,可以重用一個(gè)對(duì)象
*/
// bad
const Clock = (props) => {
return <h1 style={color: '#ccc'}>小姐姐真好看<h1/>
}
// good
const style = {
color: '#ccc'
}
const Clock = (props) => {
return <h1 style={style}>小姐姐真好看<h1/>
}
組件樣式
1. 不同組件中相同的元素樣式相互影響,a.js與b.js中都有h1組件,樣式之間會(huì)相互影響
2. 添加styled-jsx/styled-component支持
// a.js
index.css
h1: {
color: red
}
<h1>a</h1>
// b.js
<h1>b</h1>
// a.js
.a h1: {
color: red
}
<div className='a'>
<h1>a</h1>
<div/>
// b.js
<div className='b'>
<h1>a</h1>
<div/>
狀態(tài)組件與無(wú)狀態(tài)組件分割
1. 軟件設(shè)計(jì)原則"責(zé)任分離",就是讓一個(gè)模塊責(zé)任盡量少,每個(gè)模塊專(zhuān)注于一個(gè)功能,有利于代碼的維護(hù)
2. react中數(shù)據(jù)渲染頁(yè)面,UI = f(data),但是盡量把獲取和管理數(shù)據(jù)與界面渲染分開(kāi),即把獲取數(shù)據(jù)與管理數(shù)據(jù)的邏輯放在父組件,把渲染界面的邏輯放在子組件
3. A組件渲染B組件時(shí),即使props傳入一樣,B組件也會(huì)完整走一遍渲染流程
4. 函數(shù)形式的react組件,好處是不需要管理state,占用資源少,但是函數(shù)組件無(wú)法利用shouldComponentUpdate來(lái)優(yōu)化減少渲染
5. PureComponent中實(shí)現(xiàn)的shouldComponentUpdate會(huì)對(duì)傳入的參數(shù)進(jìn)行淺比較,當(dāng)傳入的props為一個(gè)對(duì)象之類(lèi)的,比如由{a: 1, b: 2} => {a: 2, b: 2},可能會(huì)出現(xiàn)UI未更新的情況出現(xiàn)
6. shouldComponentUpdate函數(shù),每次渲染render函數(shù)之前會(huì)被調(diào)用,返回true繼續(xù)渲染,返回false立刻停止,可以對(duì)深層次的數(shù)據(jù)處理
7. 在使用PureComponent時(shí),盡量在render定義之外創(chuàng)建所有對(duì)象,數(shù)組和函數(shù),并確保在各種調(diào)用間,不發(fā)生更改 - 參考table例子
8. React.memo淺比較
export default class A extends React.Component {
state = {
joke: null
}
componentDidMount() {
fetch(url, {header: {'Accept': 'application/json'}})
.then(res => {
return res.json()
})
.then(json => {
this.setState({joke: json.joke})
})
}
render() {
return <B value={this.state.joke} />
}
}
export default class B extends React.Component {
render() {
return (
<div>
<img src={url} />
{this.props.value || 'loading...'}
</div>
)
}
}
<Table
// map每次返回一個(gè)新數(shù)組,淺比較失敗
rows={rows.map()}
// 枚舉對(duì)象總不相同 - 盡量不寫(xiě)行內(nèi)樣式
style={{color: '#ccc'}}
// 箭頭函數(shù)每次都會(huì)重新渲染 - 組件層級(jí)不通過(guò)箭頭函數(shù)綁定事件
onUpdate={() => {}}
/>
高階組件
待補(bǔ)充...
render props模式
1. render props的形式
2. render props其實(shí)就是依賴(lài)注入
3. 如何利用render props實(shí)現(xiàn)共享組件之間的邏輯
4. 依賴(lài)注入:**當(dāng)邏輯A依賴(lài)邏輯B時(shí),如果讓A直接依賴(lài)B,A做不到通用。依賴(lài)注入就是把B的邏輯以函數(shù)形式傳遞給A,讓A和B之間只需要對(duì)函數(shù)接口達(dá)成一致 - 組件A和B之間,不是把B組件寫(xiě)在A組件中,而是通過(guò)把B組件當(dāng)參數(shù)傳到A組件當(dāng)中**
// 用戶(hù)登錄與未登錄情況下顯示不同的組件
const Auth = (props) => {
const userName = getUserName()
if(userName) {
const allProps = {userName, ...props}
return (
<React.Fragment>
{props.login(allProps)}
</React.Fragment>
)
} else {
return (
<React.Fragment>
{props.noLogin(props)}
</React.Fragment>
)
}
}
<Auth
login={({userName}) => <h1>Hello {userName}</h1>}
noLogin={() => <h1>Please login</h1>}
/>
提供者模式
1. 提供者模式,為了**解決組件間跨層級(jí)的信息傳遞**,A-B-C-D-...-X,當(dāng)數(shù)據(jù)要從A傳遞到X時(shí),通過(guò)props傳遞時(shí),會(huì)經(jīng)過(guò)B-C-D...,然而在B-C-D...中不需要使用props中的數(shù)據(jù),而且組件在變動(dòng)時(shí),容易出現(xiàn)props傳遞錯(cuò)誤的情況出現(xiàn)
2. 提供者模式,本質(zhì)由2個(gè)角色組成,一個(gè)叫'提供者’,一個(gè)叫'消費(fèi)者’,提供者(A)位于組件樹(shù)靠上的位置,消費(fèi)者(X)處于靠下的位置
3. 實(shí)現(xiàn)方式,通過(guò)**react提供的context功能進(jìn)行實(shí)現(xiàn)(v16.3.0+),context功能可以創(chuàng)造一個(gè)'上下文’,在這個(gè)上下文之中的所有組件都可以訪問(wèn)到同樣的數(shù)據(jù)**
4. 參考react-app中x2代碼
組合組件
1. 模式(Pattern) = 問(wèn)題場(chǎng)景(Context) + 解決方法(Solution)
2. 解決的問(wèn)題,**父組件想傳遞一些信息給子組件,但是通過(guò)props傳遞又很麻煩時(shí)**
組件狀態(tài)
1. UI = f(data),data = props + state,props代表外部傳進(jìn)來(lái)的數(shù)據(jù),state代表組件內(nèi)部狀態(tài)
2. 什么樣的數(shù)據(jù)可以存放在成員變量中?
3. 如果數(shù)據(jù)由外部傳入,放在props中
4. **如果是內(nèi)部組件數(shù)據(jù),這個(gè)數(shù)據(jù)的更改是否應(yīng)該立刻引發(fā)一次組件的重新渲染,如果是,放在state中,不是就放在成員變量中**
5. state不會(huì)被同步修改,在react的生命周期中或者處理函數(shù)中同步調(diào)用,setState不會(huì)立即同步state和重復(fù)渲染,但是如果setState由其他條件引發(fā),就有可能不是這樣了 - 在生命周期中或者事件函數(shù)中調(diào)用時(shí),react會(huì)打開(kāi)一個(gè)類(lèi)似標(biāo)記的東西,標(biāo)記打開(kāi)的過(guò)程中,setState調(diào)用都是往任務(wù)隊(duì)列里放任務(wù),當(dāng)函數(shù)調(diào)用結(jié)束時(shí),批量處理任務(wù)隊(duì)列,關(guān)閉標(biāo)記
6. 函數(shù)式setState - 當(dāng)setState的第一個(gè)參數(shù)為函數(shù)時(shí),任務(wù)列表上增加的就是一個(gè)可執(zhí)行的任務(wù)函數(shù),react沒(méi)處理完一個(gè)任務(wù),都會(huì)更新一次state,然后把新的state傳遞給這個(gè)任務(wù)函數(shù)
// 代碼1
class Foo extends Component {
foo = 'foo'
render() {
return <div>{this.foo}</>
}
}
// 代碼2
this.state = {
count: 0
}
this.setState({count: 1})
console.log(this.state.count) // 0
// 代碼3
setTimeout(() => {
this.setState({count: 2})
console.log(this.state.count) // 2
})
// 代碼4 - 結(jié)果為1 三個(gè)任務(wù)都給了this.state.count一個(gè)值
this.state = {
count: 0
}
this.setState({count: this.state.count + 1})
this.setState({count: this.state.count + 1})
this.setState({count: this.state.count + 1})
// 代碼5 - 函數(shù)式setState,結(jié)果為3
// this.setState((preState, props) => ({})
function increment(state, props) {
return {count: state.count + 1}
}
this.state = {
count: 0
}
this.setState(increment)
this.setState(increment)
this.setState(increment)
redux使用模式
1. 使用場(chǎng)景 - 復(fù)雜應(yīng)用下的狀態(tài)維護(hù),類(lèi)似于一個(gè)全局的store,**并且store只有接受某些'事件’(action),才可以修改store上的數(shù)據(jù),store對(duì)這些'事件’的響應(yīng),就是修改狀態(tài)(修改狀態(tài)的函數(shù)reducer)**
2. 事件(action) - 響應(yīng)函數(shù)(reducer) -store
3. 對(duì)于某個(gè)狀態(tài),是放在store中還是組件自身狀態(tài)中?
4. 看狀態(tài)是否會(huì)被多個(gè)react組件共享 - 不同級(jí)組件件的數(shù)據(jù)共享
5. 看這個(gè)組件被unmount之后重新被mount,之前的狀態(tài)是否需要保留 - 組件銷(xiāo)毀后又重新渲染,之前的輸入是否要保留
6. 代碼組織方式 - 基于角色分類(lèi)(modal,services,view,action,reducer)和基于功能分類(lèi)(安裝功能所在模塊分類(lèi))
7. connect函數(shù)接受兩個(gè)參數(shù),一個(gè)mapStateToProps是把Store上的state映射為props,另一個(gè)mapDispatchToProps是把回調(diào)函數(shù)類(lèi)型的props映射為派發(fā)action,connect函數(shù)調(diào)用會(huì)產(chǎn)生一個(gè)'高階組件’
8. react-redux中使用了三個(gè)模式 - 提供者模式,高階組件,有狀態(tài)組件和無(wú)狀態(tài)組件
// 代碼1
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import store from './store'
const store = createStore(store)
<Provider store={store}>
{Provider之下的所有組件都可以connect到給定的store}
</Provider>
// 代碼2
const Button = ({count, onIncrement}) => {
return (
<div>
<div>{count}</div>
<button onClick={onIncrement}>+</button>
</div>
)
}
// 代碼3
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {
count: count + 1
}
}
const mapDispatchToProps = (dispatch) => {
onIncrement: () => dispatch({type: 'INCREMENT'})
}
const Counter = connect(mapStateToProps, mapDispatchToProps)(Button)
mobx使用模式
待補(bǔ)充...
redux與mobx模式對(duì)比
待補(bǔ)充...
路由 react router
1. 單頁(yè)應(yīng)用 - 把URL映射到對(duì)應(yīng)的頁(yè)面來(lái)處理,頁(yè)面之間切換做到局部更新
2. 動(dòng)態(tài)路由 - 指的路由規(guī)則不是預(yù)先確定的,而是在渲染過(guò)程中確定的(React Router v4)
3. 靜態(tài)路由 - 路由規(guī)則是固定的
未完待續(xù)...
|