新版本的react中,刪除了一些生命週期,目前推薦使用的生命週期爲如下幾種:前端
首次渲染:vue
一、constructor,可寫可不寫,主要實現綁定this,初始化state的做用node
二、render,應該是個純函數,絕對不能有反作用產生,好比setState。這個函數的做用僅僅是返回jsxreact
三、componentDidMountwebpack
更新:web
一、shouldComponentUpdate(nextProps,nextState):必須返回true或false,決定在此次更新中是否要從新渲染。面試
二、render算法
三、componentDidUpdateredux
卸載:設計模式
componentWillUnmount:這個方法一般用來作一些清理工做,如,刪除定時器,刪除一些非react手段產生的dom。
一、函數組件:接收props,輸入jsx;沒有狀態,沒有生命週期,沒有實例。
二、受控組件:表單組件的value是受控於state的
三、非受控組件:非受控於state,經過ref來獲取到原生的value。當咱們必需要操做dom時,使用非受控組件,例上傳文件
ReactDOM.createPortal(child, container) ,由ReactDom提供的接口。 能夠實現將子節點渲染到父組件DOM層次結構以外的DOM節點。子組件可以在視覺上 「跳出(break out)」 其容器。例如,對話框、hovercards以及提示框
const myComponent = React.lazy(()=>import('./myComponent.js'))
<React.Suspense fallback={<div>loading...</div>}>
<myComponent/>
</React.Suspense>
複製代碼
ref是react提供的一種訪問dom節點或組件實例的方法,給標籤設置ref,獲取的是dom對象;給組件設置ref,獲取的是組件的實例。
一、當用到組件上時,必須是類組件,由於函數組件沒有實例。
二、在函數組件中,能夠用createRef,也能夠用useRef。這兩種方法均可以獲取相應的dom節點。可是useRef除了能夠獲取dom節點,還能夠用來保存一個在整個生命週期中不變的值,這一點在函數組件中很是重要!由於函數組件從新渲染後會返回新的值,這也是二者的區別!適合配合hook組件來使用
// class組件
myRef1 = React.createRef()
myRef2 = React.createRef()
componentDidMount(){
this.myRef1.current.focus()
}
render(){
return <div> <input ref={this.myRef1}>ref的用法</input> <Child ref={this.myRef2}/> </div>
}
// const myRef = React.useRef(初始值)
返回一個對象 {current:初始值}
複製代碼
HOC是高階組件,高階組件就是一個函數接收一個組件做爲輸入,而後返回一個新的組件做爲結果。高階組件經過使用方式,能夠分爲兩類,代理方式的高階組件和繼承方式的高階組件。
代理方式高階組件:說白了就是在新組件中render接收的參數組件。在這一過程當中,咱們能夠對props進行處理,並傳入參數組件中,以此來達到不一樣的效果。典型的例子就是react-redux的原理。執行connect函數,返回一個高階組件,他在接收一個組件做爲參數。
高階組件用於擴展的主要方式就是經過修改props
connect(mapStateToProps,mapDispatchToPrps)(Child)
複製代碼
繼承方式高階組件:比較複雜,優先使用代理方式。
context就是一種能夠從父組件或頂級組件,向全部後代組件傳遞信息的方法。常見的使用場景就是更改主題、語言等。若是是複雜的信息,那麼就用redux
使用方法三步:建立,分發,接收
頂層組件用Provider包裹,並傳遞value,不能傳其餘的。
函數組件能夠用Consumer傳children或者用hook:useContext
類組件直接將類的私有屬性contentType設置爲建立的context
import React, { useContext } from 'react'
import ReactDOM from 'react-dom'
// 一、建立一個context
const MyContext = React.createContext('mm')
// 函數組件
function Child1() {
return (
<MyContext.Consumer> {(context) => { return <div>{context}</div> }} </MyContext.Consumer>
)
}
// 類組件
class Child2 extends React.Component {
static contextType = MyContext
render() {
return <div>{this.context}</div>
}
}
// hook用法
function Child3() {
const context = useContext(MyContext)
return <div>{context}</div>
}
class App extends React.Component {
state = {
name: 'yy'
}
click = () => {
this.setState((state, props) => {
console.log(state, props)
return { name: state.name + 1 }
})
}
render() {
return (
<MyContext.Provider value={this.state.name}> // 用Provider包裹 <Child1 /> <Child2 /> <Child3 /> <button onClick={this.click}>++++</button> </MyContext.Provider>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
複製代碼
SCU默認返回true,可是若是想讓react進行淺比較,那就用purecomponent,這樣react就會自動幫咱們進行淺比較。由於函數組件沒有生命週期,因此就用memo,他會保存一個副本,若是判斷新props不會產生影響,則返回這個緩存副本,而不會從新渲染組件。
// 類組件
class App extends React.pureComponent{}
// 函數組件
function MyComponent(props){...}
function likeSCU(prevProps,nextProps){
// 判斷新老props是否對結果有影響,返回true或false
}
export default React.memo(MyComponent,likeSCU)
複製代碼
虛擬dom本質上就是JavaScript對象,整個過程以下:
一、首先,babel會對jsx進行轉化成React.createElement('tag'/組件名,null/{屬性},[child1,child2...]/child1,child2...)形式。
2、執行createElement方法後生成vnode,這個對象會有三個屬性:標籤類型、標籤屬性和子節點。其中子節點是一個數組,若是隻是文字,那就直接字符串的形式,若是也是一個標籤,則用對象的形式,對象裏的屬性仍然是以上三種。
3、生成完這個對象後,就經過patch函數,渲染成真實的dom。
四、當對數據進行修改時,從新執行React.createElemnet方法,會生成一個新的對象。此時運用diff算法,將新老dom進行對比,對比的大體流程爲:判斷節點類型,節點的屬性,節點的子節點是否一致,若是不一致,則生成一個標識並存儲。
五、當遍diff算法運行完畢,就會生成一個數組,裏面是標誌着修改點的標識。這時候,再遍歷這個數組,經過每一個標識,來進行不一樣的操做,來修改真實的dom。從而達到以最少的操做來更新dom。
原理在於優化diff算法。
當更新時組件時,會調用diff算法。可是diff算法並無那麼智能,當咱們渲染列表時,假如咱們只是更改了順序,而沒有作其餘任何操做,diff算法對比新舊vdom,會認爲是props改變了,就會從新渲染。
爲了解決這個問題,引入了key,key是react中的保留字段,他要是惟一的,不能變的,不能用index,由於若是數組的順序變了,diff時就會產生一些錯亂。若是加上了key,react就能知道新舊vdom只是換了位置,他就會採用另一種方式,好比建立新的,刪除舊的,或者其餘方式。固然,其餘的組件也會觸發更新,只不過不會改變props了。
一、什麼是合成事件系統
react會把全部事件綁定到結構的最外層,使用一個統一的事件監聽器,他經過一個映射來保存全部的事件監聽和相應的處理函數,當組件掛載和卸載時,就會在這個事件監聽器上增長或刪除映射。
二、事件系統有什麼好處
解決全部瀏覽器的兼容性問題(好比在IE9如下,阻止事件冒泡的方法不一樣,但react進行統一兼容。還有在低版本的IE瀏覽器上,不能直接獲取事件對象,react也進行了兼容處理,直接在處理函數中獲得事件對線)、簡化事件處理和回收機制,提高效率(原理相似於事件委託機制)。
3、如何使用原生事件
react中event.nativeEvent是原生的事件對象,必定要在組件銷燬的時候手動清除,不然會致使內存泄漏。
4、合成事件與原生事件一同使用的問題
合成事件其實是綁定到最外層,經過e.stopPropagation()方法來阻止合成事件冒泡,可是這種方式沒法阻止原生事件冒泡。因此,當外層一樣綁定了一個原生事件時,就會被觸發。儘可能不要原生和合成事件混用。若是混用,能夠在原生事件中經過e.target來判斷來源,進行處理。
5、合成事件只支持了事件冒泡機制,而沒有事件捕獲機制。合成事件並不能支持全部種類的事件,它只是原生事件類型的一個子集。
7、與vue中不一樣,vue中target和currentTarget都是寫事件的那個元素,而react中target是觸發的元素,currentTarget是綁定事件的元素,也就是document
event.nativeEvent.target 觸發事件的元素--->寫事件的元素
event.nativeEvent.currentTarget 綁定事件的元素--->document
複製代碼
一、只能經過setstate方法改變state的值
不能直接更改state的值。也不能在setstate時使用數組push、pop、splice等方式直接改變原數據的方法,對象也不能直接改變其屬性值。
實際上,這樣寫有時候頁面也能正常展現,可是可能會有很嚴重的bug。例如:當咱們在用SCU優化時,若是咱們判斷新老name屬性是否相等,而在父組件上,若是咱們向下面這樣寫,至關於先改變了state,再用setState觸發更新,這會致使在子組件中nextProps和props是徹底同樣的,就不會更新。例如:
this.state.name = 'xxx'
this.setState({name:this.state.name})
或
this.setState({arr:this.state.arr.push(111)})
複製代碼
// 錯誤1
this.state.count++
// 錯誤2
this.setState({arr:this.state.arr.push/pop/shift/slice...(直接改變原數組的)(1)})
// 錯誤3
this.state.obj.name= 'xxx'
this.setState({obj:this.state.obj})
複製代碼
二、setstate有兩種寫法
// 第一個參數是要改變的對象,第二個參數是回調函數,會在改變完成後執行
this.setState({},()=>{})
// 第一個參數是函數,接受兩個參數,第一個函數在更新前運行,第二個在更新後運行
this.setState((state,props)=>{},(state,props)=>{})
複製代碼
三、setState異步仍是同步
setState有時是同步,有時是異步。關鍵在與batch update機制,react並非執行全部代碼時都能啓動這個機制,只有在受到react管理的入口中,纔會觸發機制,如:生命週期(及其中調用的函數)、react中註冊的事件(及其中調用的函數)。而setTimeout、setInterval、自定義的dom事件,這些並非受到react管理的,這些沒有batchupdate機制。
batchupdate即批量更新,react爲了提升渲染效率,會把更新操做集中起來處理,因此,在函數運行完畢前,react沒有着急去setState,而是等到運行結束,把全部的setState集中起來統一處理。因此,就變成了異步。
batch update機制
click=()=>{
// 開始,處於batch update
// 能夠想象成,在函數開始執行時 變量isBatchingUpdate = true
// 由於處於batch update中,因此爲異步的
this.setState({count:this.state.count+1},()=>{console.log(this.state.count)})
console.log(this.state.count)
// 結束,變量isBatchingUpdate = false
}
click=()=>{
// 開始,處於batch update
// 能夠想象成,在函數開始執行時 變量isBatchingUpdate = true
// 由於setTimeout是異步的,因此並無當即執行setState,而當執行setState時,isBatchingUpdate已經爲false,即不處於batch update階段,因此會同步執行代碼
setTimeout(()=>{
this.setState({count:this.state.count+1})
// 此處能夠得到最新的值
console.log(this.state.count)
})
// 結束,變量isBatchingUpdate = false
}
// 這個原理與上面的相同
document.body.addEventListen('click',()=>{
this.setState({count:this.state.count+1})
// 此處能夠得到最新的值
console.log(this.state.count)
})
複製代碼
一道經典面試題:
五、setState多個可能被合併成一個
這樣寫會被合併,爲何?
前面說過,若是命中了batch機制,會在batch結束時統一進行處理,而這個時候,this.state.count的值都是同樣的,因此,至關於執行了三遍this.setState(0+1),可想而知,其實最後一次的纔會起到最終做用。
this.setState({count:this.state.count+1})
this.setState({count:this.state.count+1})
this.setState({count:this.state.count+1})
而若是時函數寫法,就不會被合併。由於函數參數中的state老是接受最新的state
this.setState((state,props)=>({count:state.count+1}))
this.setState((state,props)=>({count:state.count+1}))
this.setState((state,props)=>({count:state.count+1}))
複製代碼
一、shouldComponentUpdate是react組件的一個生命週期。在父組件更新時,子組件會調用這個函數,若是這個函數返回true,則會從新渲染,false則不渲染。react默認返回true
二、既然默認返回true,那麼若是父組件從新渲染,子組件無論props有沒有變化,都會從新渲染。這樣時耗費性能的一件事,解決這個問題咱們就要靠SCU優化組件何時應該渲染。
三、在優化時,咱們判斷新老props是否相等,若是時基本類型的值,則直接===判斷。若是是引用類型,就比較麻煩,咱們須要深度比較,lodash有一個isEqual方法,能夠幫咱們比較,可是這個方法很浪費性能。因此,若是每次從新渲染都要比較相等的化,那麼可能反而比直接從新渲染更慢。因此,react並無幫咱們去比較,而是把這個能力交給使用者去決定。
四、若是頁面沒有性能優化的需求時,咱們通常不用去刻意優化,若是稍有不慎,可能會致使bug,或者使頁面更卡。若是想用SCU優化的化,建議傳輸props時,用基本類型,這樣比如較。
shouldComponentUpdate(nextProps,nextState){
if(nextProps.name===this.props.name){
return false
}else{
return true
}
}
複製代碼
首次渲染:constructor-->render-->componentDidMount,將jsx經過babel轉爲React.createElement函數的形式。React.createElement執行會返回一個虛擬dom,虛擬dom再經過patch函數,轉爲真實的dom,渲染到頁面上。
組件更新:shouldComponentUpdate-->render-->componentDidUpdate,獲得新的虛擬dom後,經過patch函數,將新舊進行對比:比較節點名、節點屬性、子元素。而後得出最小的改動方案,再去操做更改dom。
redux是一個獨立的JavaScript狀態管理工具。除了react,他也能夠搭配其餘框架使用,如vue、angular。固然,react也能夠和其餘狀態管理工具配合,如mobx,flux等。
另外,redux的做者爲了讓redux能與react更好的搭配使用,開發了react-redux。
一、action:本質上是 JavaScript 普通對象,action 內必須使用一個字符串類型的type字段來表示將要執行的動做指令。
二、reducer:必須是純函數,接收兩個參數,state和action,並返回一個對象,返回的這個對象即爲最新的state。根據接受的指令(action),改變當前的狀態(state),並返回最新的狀態。
三、createStore:能夠接收三個參數,第一個參數是reducer函數(必傳),第二個參數是初始state,第三個參數是增強器。第二個參數若是是對象,就會被當成初始state,若是第二個參數是函數,就會被當成store enhancer(增強器,不是中間件)。調用函數會返回一個store對象。
四、store.getState():不須要參數,返回值是state樹。
五、store.dispatch(action):改變state樹的惟一途徑。他會分發action,並調用reducer函數,reducer函數根據action,來改變state。他接受一個參數action。
六、store.subscribe(listener):訂閱方法,當執行這個函數後,會添加一個監聽器。接收一個回調函數做爲參數,dispatch時,會觸發回調函數。在回調函數裏,咱們能夠用store.getState()方法獲取最新狀態,判斷state是否改變,從而進行一些其餘操做,它會返回一個函數,執行這個函數,能夠取消監聽。
七、store.unsubscribe(listener):取消訂閱的方法。
八、combineReducers(reducers):隨着應用變得愈來愈複雜,能夠考慮將 reducer 函數 拆分紅多個單獨的函數,拆分後的每一個函數負責獨立管理 state 的一部分。在createStore的時候,咱們還要手動把多個函數合成一個函數,最終合成相似下方的函數:
function a (state,action){
return {...}
}function b (state,action){
return {...}
}
combineReducers({a,b})
// 將上面兩個轉化爲下面一個
function root(state,action){
return {
a:a(state.a ,action),
b:b(state.b ,action)
}
}
複製代碼
因此針對a 傳入的都是state.a。可是假如某個場景時某個reducer須要的不是某個state,而是所有state,那麼用這種寫法就會出錯。解決這個問題有不少思路:咱們能夠把須要的數據放到action裏、再添加一個reducer來適應這種特殊場景。reduce-reducers庫能夠簡化上述過程、最笨的方法,在寫reducer時,規定好特殊狀況的處理方法。
九、applyMiddleware(函數或數組),接收一個參數,能夠是一個middleware函數,多個middleware時,傳入一個數組。它返回一個使用middleware的store enhancer。這個增強器是增強dispatch,當還有其餘增強器的時候,要用compose方法鏈接,applymiddleware要放到compose的第一個參數。
增強器是用來增強store的功能,最多見的是增強dispatch,而增強dispatch的函數,咱們叫作中間件,applyMiddleware就是使用中間件,返回一個增強器。當咱們想要增強多個方面時,就須要多個增強器,此時就要用到compose函數,把多個增強器合成一個函數,由於createStore方法,只能接受一個增強器的參數。
中間件的本質就是替換原生的store的dispatch方法,讓他在dispatch的時候不是直接到reducer,而是通過一些處理,中間件能夠鏈接,從而實現串聯。
加強器的本質其實就是加強store,store是一個對象,除了保存了一些狀態,還有一些方法,如dispatch,getState,subscribe,replaceRudecer。每個函數均可以被修改。經常使用的模式就是先把原生的方法保存,而後讓原來的方法賦值爲新的函數,在這個新的函數里加入一些功能,每每最後仍是調用原生的方法。加強器還能夠給store增長新的方法。
必定要把applyMiddleware放到最左邊,compose的時候,從右到左合成,也就是最左邊的是最後合成時包在最外層的,最早執行。這樣作防止,一些異步操做致使其餘store enhancer之間的衝突。
import {createStore,compose,applyMiddleware} from 'redux'
import CreateThunkMiddleware from 'redux-thunk'
const enhancers = compose(applyMiddleware(thunkMiddleware),xxx其餘增強器)
createStore(rootReducer,enhancers)
複製代碼
經過context,讓每一個子組件能夠獲取store,而後經過connect函數,將子組件外包一層組件,在這層組件中進行state的數據處理,在經過props的方式傳遞給子組件
複製代碼
import MyContext from './context' // 模擬實現
import React from 'react'
function connect(mapStateToProps, mapDispatchToProps) {
return function (Component) {
class HOC extends React.Component {
static contextType = MyContext
state = {}
componentDidMount() {
this.context.store.subscribe(this.upDate)
}
componentWillUnmount() {
this.context.store.unsubscribe(this.upDate)
}
upDate = () => {
this.setState({})
}
render() {
const store = this.context.store
const newProps = {
...this.props,
...mapStateToProps(store.getState(), this.props),
...mapDispatchToProps(store.getState(), this.props)
}
return <Component {...newProps} />
}
}
return HOC
}
}
複製代碼
一、是一個函數。
二、接收兩個參數,state和ownProps。第一個是必傳的,當只傳第一個參數的時候,只有當store發生變化時,會調用mapStateToProps。 當指定ownProps時,這個ownProps表明的是從父組件傳入的props,此時,只要props變化,就會從新觸發mapStateToProps。
一、能夠是函數,也能夠是對象。
二、若是傳遞的是一個對象,那麼每一個定義在該對象的函數都將被看成 Redux action creator,對象所定義的方法名將做爲屬性名;每一個方法將返回一個新的函數,函數中dispatch方法會將action creator的返回值做爲參數執行。這些屬性會被合併到組件的 props 中。
三、若是是一個函數,能夠接收兩個參數,dispatch和ownProps。規則同上。返回一個對象,這個對象會和props合併
四、若是不指定,默認狀況下,dispatch會注入到你的組件 props 中
在沒有使用redux時,咱們請求數據都是直接在react組件裏完成了,可是當狀態變得複雜時,咱們就須要用到redux了,此時請求數據的過程,應該也放到redux裏進行處理,獲得結果後,直接塞到redux裏,纔是一個合理的設計,因此纔有了redux異步的需求。
藉助中間件 如redux-thunk、redux-saga
import CreateSagaMiddleware,{takeEvery,takeLatest} from 'redux-saga'
import {put,call,take,} from 'redux-saga/effects'
const saga = new CreateSagaMiddleware()
createStore(reducer,applyMiddleware(saga))
saga.run(root)
function *root(){
yield takeEvery(...)
}
put dispatch
call 調用函數
複製代碼
頁面觸發一個action--->dispatch中間件處理--->reducer返回新的state--->從新渲染
一、合理使用shouldComponentUpdate
react中的shouldComponentUpdate默認返回true,根據狀況合理進行優化。或者使用React.pureComponent、React.memo
SCU默認返回true,可是若是想讓react進行淺比較,那就用purecomponent,這樣react就會自動幫咱們進行淺比較。
class App extends React.pureComponent{}
在函數組件中,由於沒有生命週期,因此react也不能幫咱們去判斷,因此就用就用React.memo,
他會保存一個副本,若是判斷新props不會產生影響,則返回這個緩存副本,而不會從新渲染組件
function MyComponent(props){
...
}
function likeSCU(prevProps,nextProps){
// 判斷新老props是否對結果有影響,返回true或false
}
export default React.memo(MyComponent,likeSCU)
複製代碼
2、合理使用mapStateToProps
react-redux至關於在組件外包一層容器組件,而這個容器組件內的shouldComponentUpdate函數,是進行了淺比較的,因此,在connect裏傳入的mapStateToProps函數,必定要只返回組件用到的數據,不能直接返回整個state,若是返回了整個state,那麼每一次數據改變,組件都會從新渲染,浪費性能
此外,若是咱們不傳mapStateToProps函數,那麼就至關於不接收store裏的數據,只接收從dom上傳遞的數據。這樣作雖然沒有實際意義,但也是側面進行了優化,他會檢測父組件傳來的props是否有變化,若是沒有變化,不會更新子組件。React.pureComponent實現了這一功能,也是進行了一次淺比較。
3、合理傳遞props
當咱們向子組件傳遞props時,儘可能不要直接傳入一個對象,或匿名函數,這樣作會致使每次從新渲染時,都會產生一個新的對象,即便數據沒變,也會致使子組件從新渲染。
// 錯誤
<Child style={{color:'red'}}/>
// 正確
const fooStyle = {color:'red'}
<Child style={fooStyle}/>
複製代碼
當咱們傳入一個匿名函數的時候,通常都是爲了傳入一些參數。可是這樣作就是使每一次從新渲染時,dom都會更新,致使浪費性能。若是想解決這個問題,咱們能夠將dom變成一個組件。將須要用到的參數經過props傳入這個組件中,調用函數時,就不用傳入一個匿名函數了。
<Child onClick={()=>this.fun(item.id)}/>
<Child id={item.id} />
mapStateToProps=(dispatch,ownProps)=>{
cosnt {id} = ownProps
return {
fun:()=>{fun(id)}
}
}
複製代碼
4、渲染列表時使用key,避免浪費性能,另外注意,雖然key是props傳遞的,可是組件內接受不到這個props,由於key和ref是react中保留的兩個特殊prop。
五、 在componentwillunmount中銷燬事件綁定
六、合理使用異步組件
七、不要再render裏使用bind(this),由於每次從新渲染都會從新bind
八、webpack進行優化
九、前端通用的優化手段,懶加載
一、經過緩存來減小計算次數
前面說過,咱們在mapStateToProps函數裏返回的必定是組件中用到的值,否則會引起組件的從新渲染,形成浪費。那麼若是咱們在mapStateToProps中返回的值,須要通過一層複雜的過濾,那麼豈不是每次store更新時,都會進行一次複雜的運算。沒錯,這樣確定時浪費資源的。爲了解決這個問題,咱們須要一種緩存機制,即對比新舊的數據,若是沒有發生變化,咱們就直接返回上一次存儲的數據。它就是:reselect!
以todo應用爲例:
const selectTodos = (todos,filter)=>{
switch(){
case 'xxx':return todos.filter(item=>item.completed)
...
}
}
const mapStateToProps = (state)=>{
return {
todos:selectTodos(state.todos,state.filter)
}
}
// 使用reselect後
import {createSelector} from 'reselect'
// 調用createSelector函數後,返回一個函數,它接收state
export const reselectTodos = createSelector(
// 第一個參數是函數數組,每一個函數返回的值會進行比較,若是相同,則直接返回上一次計算結果。
[state=>state.filter,state=>state.todos],
// 若是不相同,則用函數數組中每一個函數的返回值當參數,傳入第二參數函數中,從新計算。
(filter,todos)=>{
switch(){
case 'xxx':return todos.filter(item=>item.completed)
...
}
})
const mapStateToProps = (state)=>{
return {
todos:reselectTodos(state)
}
}
複製代碼
二、經過合理設計redux數據結構,來減小計算複雜度
假如咱們給在傳統todo項目的基礎上,增長一個表示是否爲緊急狀態的數據。那麼這時候咱們有兩種設計模式。
// 反範式化
id:1,
text:'xxx',
completed:false,
type:{
name:'緊急',
color:'red'
}
// 或者,範式化
{
id:1,
text:'xxx',
completed:false,
typeId:1
}
[
{
id:1,
name:'緊急',
color:'red'
},{
id:2,
name:'還行',
color:'green'
}
]
複製代碼
在項目中,咱們應該儘可能使用範式化的數據結構。由於,當咱們去更改一個項目的緊急程度所對應的顏色時,會很是方便,而若是時反範式化,則要遍歷全部的對象。雖然範式化多了一次根據typeId匹配狀態對象的計算過程,可是由於前面咱們用了reselect緩存,因此並不會增長性能消耗!