這是 pastate 系列教程的第二章,歡迎關注,持續更新。javascript
這一章,咱們在上一章的 state 結構中添加多一些信息,並用多個組件來組織 pastate 應用。html
咱們把上一章的我的基本信息數據包裝爲 state.basicInfo
屬性的對象,並向 state
中添加 address
屬性,保存我的地址信息:vue
const initState = { basicInfo: { name: 'Peter', isBoy: true, age: 10 }, address: { country: 'China', city: 'Guangzhou' } }
因爲 JavaScript 語言的限制,pastate 不能檢測到經過賦值來爲對象添加新屬性,以自動把新屬性轉化爲響應式節點。因此你應該在 initState 中把須要用到的 state 屬性都定義出來,把屬性值初始化爲 null 或空數組都是能夠的。下面是個錯誤的例子:java
const initState = { basicInfo: ..., address: ... } const store = new Pastore(initState) const state = store.state state.hobby = 'coding' // 錯誤,state.hobby 屬性不具備受 pastate 控制,不具備響應式特色
即便支持這種特性,它也會使開發者難以徹底把握 state 的結構,致使應用難以開發和維護,因此咱們應該在 initState 裏對 state 的結構進行完整的定義。react
咱們先使用一種簡單臨時的方式來構建子組件:git
... /** @type {initState} */ const state = store.state; class BasicInfoView extends Component { render(){ return ( <div style={{padding: 10, margin: 10}}> <strong>Basic info:</strong><br/> My name is {state.basicInfo.name}.<br/> I am a {state.basicInfo.isBoy == true ? "boy" : "girl"}.<br/> I am {state.basicInfo.age} years old.<br/> </div> ) } }
class AddressView extends Component { render(){ return ( <div style={{padding: 10, margin: 10}}> <strong>Address:</strong><br/> My country is {state.address.country}.<br/> My city is {state.address.city}.<br/> </div> ) } }
能夠看到,BasicInfoView 組件直接引用 store.state.basicInfo 的值,AddressView 組件直接引用 store.state.address 的值。接着修改原來的 AppView 父組件,把這兩個子組件嵌套進去,同時增長一個方法來修改 address.city
的值:github
... class AppView extends Component { increaseAge(){ state.basicInfo.age += 1 } decreaseAge(){ state.basicInfo.age -= 1 } changeCity(){ state.address.city += '!' } render() { return ( <div style={{padding: 10, margin: 10, display: "inline-block"}}> <BasicInfoView /> <AddressView /> <button onClick={this.decreaseAge}> decrease age </button> <button onClick={this.increaseAge}> increase age </button> <button onClick={this.changeCity}> change city </button> </div> ) } } ...
完成!讓咱們運行一下: redux
點擊按鈕,看起來一切正常!咱們經過 Chrome 的 react dev tools 來觀察一下當 state 改變時,各個組件的渲染狀況。打開瀏覽器的開發者工具,選擇 react 標籤,勾選上 Highlight Updates, 這時當組件從新渲染時,會被帶顏色的方框框起來。 segmentfault
咱們點擊頁面上 decrease age
按鈕試試,組件從新渲染的結果以下: api
咱們能夠發現,當只有 state.basicInfo.age 更改時,AppView、BasicInfoView 和 AddressView 3個組件都會被從新渲染,即便 AddressView 所引用的數據沒有發生任何改變!這是 react 多組件渲染的一般狀況,當應用組件簡單、嵌套層級很少時,咱們不會感受到這種模式會帶來什麼明顯的影響;可是當應用組件的嵌套關係變得比較複雜的時候,會帶來性能隱患,咱們須要來關注這個問題。
先介紹一下 store 中的兩個不一樣的 state:store.imState 和 store.state ,你能夠嘗試瞭解一下:
響應式影子
, 能夠對 store.state 任何節點進行直接賦值修改,pastate 會把修改結果做用到 store.imState,並異步觸發視圖更新。或者簡化爲如下兩點:
這兩個概念對於沒有使用過 redux 和沒了解過 vue.js 原理的人來講可能有點難以理解。不過不要緊,不理解這兩個概念並不妨礙你使用 pastate,你能夠在使用 pastate 的過程當中徹底感受不到 imState 的存在。pastate 的理念就是封裝複雜概念,讓你能夠用一種簡單的方式去實現複雜的功能。
若是你想要理解 pastate 的詳細原理,能夠查看原理章節。
當一個 component 與 store 鏈接時,store 會把 imState 傳遞到 component 的 props
.state 中,所以咱們能夠在 AppView 組件的 props 中接收 state,同時把 AppView 組件的基類改成 react 純組件 PureComponent,這樣就開啓了組件按需渲染效果:
import React, { PureComponent } from 'react'; // 1. 改用 PureComponent 代替 Component ... class AppView extends PureComponent { // 1. 改用 PureComponent ... render() { /** @type {initState} */ let state = this.props.state; // 2. 從 props 接收 state return ( <div style={{padding: 10, margin: 10, display: "inline-block"}}> {/** 3. 把 state 的子節點傳遞給對於的子組件 */} <BasicInfoView state={state.basicInfo}/> <AddressView state={state.address}/> ... </div> ) } } ...
注意上面代碼的第3點註釋,咱們把 state 數據的子節點經過 props 傳遞給子組件: <BasicInfoView state={state.basicInfo}/>
。對於不直接與 store 直接鏈接的子組件,咱們一樣也須要修改成從
props 獲取 state, 並把組件的基類改爲 PureComponent:
class BasicInfoView extends PureComponent { // 1. 基類改成 PureComponent render(){ let state = this.props.state; // 2. 從 props 接收 state return ( <div style={{padding: 10, margin: 10}}> <strong>Basic info:</strong><br/> {/** 3. 這裏的 state 是 basicInfo 對象 */} My name is {state.name}.<br/> I am a {state.isBoy == true ? "boy" : "girl"}.<br/> I am {state.age} years old.<br/> </div> ) } }
class AddressView extends PureComponent { // 1. 基類改成 PureComponent render(){ let state = this.props.state; // 2. 從 props 接收 state return ( <div style={{padding: 10, margin: 10}}> <strong>Address:</strong><br/> {/** 3. 這裏的 state 是 address 對象 */} My country is {state.country}.<br/> My city is {state.city}.<br/> </div> ) } }
能夠看到,分配到子組件的 props 中的 state 是 根state 的子節點。所以在 BasicInfoView 中的 this.props.state 是 basicInfo 對象, 而在 AddressView 中的 this.props.state 是 address 對象。
完成!咱們來看看運行效果!
decrease age
按鈕或 increase age
按鈕,咱們看到的組件從新渲染狀況是:
change city
按鈕,咱們看到的組件從新渲染狀況是:
Amazing!能夠看到當咱們點擊按鈕改變 state 節點時,只有引用被改變的 state 節點的組件纔會進行從新渲染, 咱們成功地實現了多組件按需渲染的效果!當應用具備大量不與 store 直接鏈接的子組件時,這種按需渲染的策略能夠大幅提升應用的渲染性能。
從 props 中接收到的 state 的每一個節點都是特殊的包裝類型 , 當須要在 if(...)
語句或 ... ? A : B
使用其布爾值結果時, 須要使用 ==
進行顯式比較來獲取,以下
class BasicInfoView extends PureComponent { render() { /** @type {initState['basicInfo']} */ let state = this.props.state; return ( <div style={{ padding: 10, margin: 10 }}> {state.isBoy == true ? "boy" : "girl"} {/* 正確 */} {state.isBoy ? "boy" : "girl"} {/* 錯誤 */} {state.age != 0 ? "Not 0" : "0"} {/* 正確 */} {state.age ? "Not 0" : "0"} {/* 錯誤 */} </div> ) } }
React 的 PureComponent 會在渲染前對新的 props / state 與老的 props / state 進行淺層比較( shallow comparison),僅當發現 props / state 發生改變時,才執行從新渲染。淺層比較便是比較 props / state 的根級屬性值是否改變,若是屬性值是數組 / 對象類型,比較的結果使其引用是否相等:
console.log(["a"] == ["a"]) // 結果是 false let a = ["a"] console.log(a == a) // 結果是 true
console.log({a: "a"} == {a: "a"}) // 結果是 false let a = {a: "a"} console.log(a == a) // 結果是 true
Pastate 符合 immutable data 規範的 state 數據,能夠確保當某個 state 節點改變時,其祖先節點的引用都會進行更新,因此能夠配合使用 PureComponent 實現高效的按需渲染。
按需渲染時須要對 state 的結構進行模塊化設計,若是把全部的屬性都放在 state 根節點上,就無法實現按需渲染了:
// 注意:這樣的 state 設計沒法實現子組件的按需渲染 initState = { name: 'Peter', isBoy: true, age: 10, country: 'China', city: 'Guangzhou' }
固然,只有當應用的 state 比較複雜且對 state 的操做比較繁多時候,纔會體現按需渲染對性能的提高;當應用比較簡單的時候,不必定要對 state 和視圖進行太詳細的劃分。
一樣,咱們可使用 jsDoc 註釋讓子組件中 state 的具備智能提示,以下:
class BasicInfoView extends PureComponent { render(){ /** @type {initState['basicInfo']} */ let state = this.props.state; ... } }
class AddressView extends PureComponent { render(){ /** @type {initState['address']} */ let state = this.props.state; ... } }
請使用 xxx['xxx'] 的格式指明對象的子節點: /** @type {initState['address']} */
。在 vs code 裏,暫時沒法使用 xxx.xxx 的嵌套格式指定一個變量的類型。
若是某組件只在視圖中出現一次,那麼這種組件被稱爲單實例組件。這種組件能夠把對子組件設計的 state 操做函數簡單地封裝在子組件內部,提升組件的內聚性,便於維護管理。下面以 BasicInfoView 爲例,把操做按鈕移入子組件,並把兩個操做函數移入子組件:
... class BasicInfoView extends PureComponent { increaseAge(){ state.basicInfo.age += 1 } decreaseAge(){ state.basicInfo.age -= 1 } render(){ /** @type {initState['basicInfo']} */ let state = this.props.state; return ( <div style={{padding: 10, margin: 10}}> ... <button onClick={this.decreaseAge}> decrease age </button> <button onClick={this.increaseAge}> increase age </button> </div> ) } } ...
一樣,你也能夠對 AddressView 作相似的處理。
下一章, 咱們將會介紹如何在 pastate 中渲染和操做 state 中的數組。