"來源掘金小冊 react實戰:設計模式和最佳實踐" react設計思想 1. 屬性展開 保留當前組件需要的props,並且使其他的props傳遞下去 2. 在react中,界面完全由數據驅動 3. 在react中,一切都是組件 4. props是react組件之間通訊的基本方式 設計react組件 ...
react設計思想
- 屬性展開 - 保留當前組件需要的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中,界面完全由數據驅動
- 在react中,一切都是組件
- props是react組件之間通訊的基本方式
// 可以實現記錄用戶訪問路徑操作信息,每次進入某個頁面或者功能的時候都會訪問一個線上資源
// a.js
export default Beacon extends React.Component {
componentDidMount() {
const beacon = new Image()
beacon.src = 'https://sxc.image/i.png'
}
render() {
return null
}
}
// b.js
render() {
<div>
<Beacon/>
</div>
}
// 代碼組織方式優化 - renderXXX函數訪問的是同樣的props和state,代碼耦合嚴重
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) => {
}
設計react組件原則及最佳實踐
1. 保持介面小,props數量要少
2. 根據數據邊界來劃分組件,充分利用組合
3. 把state往上層組件提取,讓下層組件只需要實現為純函數
4. 避免renderXXXX函數
5. 給回調函數類型的props加統一首碼,比如on or handle - onStart
6. 使用propTypes來定義組件的props
7. 儘量每個組件都有自己專屬的文件
8. 用解構賦值的方式獲取參數props的屬性 - const { a, b } = { a: 1, b: 2 }
9. 利用屬性初始化來定義state和成員函數 - 給預設值 const { data = [] } = this.props
/*
1. 儘量不要在jsx中寫內聯函數
2. 每次渲染,都會產生新的函數對象
3. 每次傳給組件的props(方法,數據,狀態等)都是新的,無法通過shouldComponentUpdate對props的檢查來避免重覆渲染
*/
<Buttons
activated={this.state.isStarted}
onStart={() => {/* TODO */}}
onReset={() => {/* TODO */}}
/>
/*
1. style屬性的使用
2. 內聯樣式在組件每次渲染時都會創建一個新的style對象
3. 如果樣式不發生改變,應該把style對象提取到組件之外,可以重用一個對象
*/
// 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組件,樣式之間會相互影響
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/>
狀態組件與無狀態組件分割
1. 軟體設計原則"責任分離",就是讓一個模塊責任儘量少,每個模塊專註於一個功能,有利於代碼的維護
2. react中數據渲染頁面,UI = f(data),但是儘量把獲取和管理數據與界面渲染分開,即把獲取數據與管理數據的邏輯放在父組件,把渲染界面的邏輯放在子組件
3. A組件渲染B組件時,即使props傳入一樣,B組件也會完整走一遍渲染流程
4. 函數形式的react組件,好處是不需要管理state,占用資源少,但是函數組件無法利用shouldComponentUpdate來優化減少渲染
5. PureComponent中實現的shouldComponentUpdate會對傳入的參數進行淺比較,當傳入的props為一個對象之類的,比如由{a: 1, b: 2} => {a: 2, b: 2},可能會出現UI未更新的情況出現
6. shouldComponentUpdate函數,每次渲染render函數之前會被調用,返回true繼續渲染,返回false立刻停止,可以對深層次的數據處理
7. 在使用PureComponent時,儘量在render定義之外創建所有對象,數組和函數,並確保在各種調用間,不發生更改 - 參考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每次返回一個新數組,淺比較失敗
rows={rows.map()}
// 枚舉對象總不相同 - 儘量不寫行內樣式
style={{color: '#ccc'}}
// 箭頭函數每次都會重新渲染 - 組件層級不通過箭頭函數綁定事件
onUpdate={() => {}}
/>
高階組件
待補充...
render props模式
1. render props的形式
2. render props其實就是依賴註入
3. 如何利用render props實現共用組件之間的邏輯
4. 依賴註入:**當邏輯A依賴邏輯B時,如果讓A直接依賴B,A做不到通用。依賴註入就是把B的邏輯以函數形式傳遞給A,讓A和B之間只需要對函數介面達成一致 - 組件A和B之間,不是把B組件寫在A組件中,而是通過把B組件當參數傳到A組件當中**
// 用戶登錄與未登錄情況下顯示不同的組件
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. 提供者模式,為了**解決組件間跨層級的信息傳遞**,A-B-C-D-...-X,當數據要從A傳遞到X時,通過props傳遞時,會經過B-C-D...,然而在B-C-D...中不需要使用props中的數據,而且組件在變動時,容易出現props傳遞錯誤的情況出現
2. 提供者模式,本質由2個角色組成,一個叫‘提供者’,一個叫‘消費者’,提供者(A)位於組件樹靠上的位置,消費者(X)處於靠下的位置
3. 實現方式,通過**react提供的context功能進行實現(v16.3.0+),context功能可以創造一個‘上下文’,在這個上下文之中的所有組件都可以訪問到同樣的數據**
4. 參考react-app中x2代碼
組合組件
1. 模式(Pattern) = 問題場景(Context) + 解決方法(Solution)
2. 解決的問題,**父組件想傳遞一些信息給子組件,但是通過props傳遞又很麻煩時**
組件狀態
1. UI = f(data),data = props + state,props代表外部傳進來的數據,state代表組件內部狀態
2. 什麼樣的數據可以存放在成員變數中?
3. 如果數據由外部傳入,放在props中
4. **如果是內部組件數據,這個數據的更改是否應該立刻引發一次組件的重新渲染,如果是,放在state中,不是就放在成員變數中**
5. state不會被同步修改,在react的生命周期中或者處理函數中同步調用,setState不會立即同步state和重覆渲染,但是如果setState由其他條件引發,就有可能不是這樣了 - 在生命周期中或者事件函數中調用時,react會打開一個類似標記的東西,標記打開的過程中,setState調用都是往任務隊列里放任務,當函數調用結束時,批量處理任務隊列,關閉標記
6. 函數式setState - 當setState的第一個參數為函數時,任務列表上增加的就是一個可執行的任務函數,react沒處理完一個任務,都會更新一次state,然後把新的state傳遞給這個任務函數
// 代碼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 - 結果為1 三個任務都給了this.state.count一個值
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 - 函數式setState,結果為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. 使用場景 - 複雜應用下的狀態維護,類似於一個全局的store,**並且store只有接受某些‘事件’(action),才可以修改store上的數據,store對這些‘事件’的響應,就是修改狀態(修改狀態的函數reducer)**
2. 事件(action) - 響應函數(reducer) -store
3. 對於某個狀態,是放在store中還是組件自身狀態中?
4. 看狀態是否會被多個react組件共用 - 不同級組件件的數據共用
5. 看這個組件被unmount之後重新被mount,之前的狀態是否需要保留 - 組件銷毀後又重新渲染,之前的輸入是否要保留
6. 代碼組織方式 - 基於角色分類(modal,services,view,action,reducer)和基於功能分類(安裝功能所在模塊分類)
7. connect函數接受兩個參數,一個mapStateToProps是把Store上的state映射為props,另一個mapDispatchToProps是把回調函數類型的props映射為派發action,connect函數調用會產生一個‘高階組件’
8. react-redux中使用了三個模式 - 提供者模式,高階組件,有狀態組件和無狀態組件
// 代碼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使用模式
待補充...
redux與mobx模式對比
待補充...
路由 react router
1. 單頁應用 - 把URL映射到對應的頁面來處理,頁面之間切換做到局部更新
2. 動態路由 - 指的路由規則不是預先確定的,而是在渲染過程中確定的(React Router v4)
3. 靜態路由 - 路由規則是固定的
未完待續...