如何存儲 React 組件的數據
主要講解這幾個部分:state、store、static、this、module-global data
前言
隨著 React 和 Redux 的到來,一個共同的問題被問到:
我應該將數據保存在 Redux Store 中呢?還是應該保存成本地 state?
其實這個問題是說的不完整( 或者原文說的太簡單 ),因為你能在組件中存儲數據的方式還有兩種:static 和 this.(其實就是靜態數據,還是類的實例數據)
讓我們來依次討論一下,你應該在什么時候使用它們。
一、本地 state ( Local state )
ps:下面翻譯的時候將 local state => 直接翻譯成 state 吧,對于 state 其實的作用域是與組件掛鉤的,直接翻譯成本地 state 其實是不準確的。
當 React 在第一次被介紹的時候,我們就知道 state 。我們知道一個很重要的事情,就是當 state 的值改變的時候將觸發組件的 re-render(重新渲染)。
這個 state 也能傳遞給子組件,子組件中通過 props 來獲取傳遞的數據,這就允許你能夠將你的組件分為:smart data-components(智能數據組件)and dumb presentational-components (填鴨式組件)。
這里有一個使用 state 來編寫的 counter app(計數 APP):
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
this.addOne = this.addOne.bind(this)
}
addOne() {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<button
onClick={ this.addOne }>
Increment
</button>
{ this.state.counter }
</div>
)
}
}
export default App
你的數據( counter 的值 )是存儲在 App 組件中,也能向下傳遞給子組件。
& 應用場景
假如 counter 數據在你的 app 中是很重要的,以及這個數據會存儲下來用于其他組件,那么你將不希望使用 state 來保存這個值。
這最好的實踐是處理用戶接口 (UI, User Interface) 的狀態數據。比如:使用一個 交互組件 去填寫表單,這時候使用 state 是不錯的選擇。
另外的例子,就是在 UI 交互相關的數據中使用,比如你將在一個列表中記錄當前選中的 tab (選項卡)的狀態,你就能存儲一個 state 。
在你選擇 state 的使用時機,也能夠這樣考慮:你存儲的數據是否會在其他組件中使用。如果一個值是只有一個組件(或者也許只有一個子組件),這時使用 state 來保持這個值(或者說狀態)都是安全的。
總結:保持 UI 狀態和暫時的數據(比如:表單的輸入),能夠使用 state 。
二、Redux store
隨著發展,每個人開始選擇 單向數據流 , 我們選擇 Redux。
對于 Redux,我們將獲得一個全局的 store 。這個 store 將綁定在一個最高等級的組件中,然后將 App 的數據流入所有的子組件(其實整個 App 就已經是這個最高等級組件的子組件了)。你能 connect 全局 store ,使用: connect wrap 和 mapStateToProps 方法 .
首先,人們就將任何事情都交給了 Redux store 。比如:Users, modals, forms, sockets...,主要你知道的。
下面是一個和之前相同的計數 App,然后使用了 Redux。主要需要注意的是 counter 數據,使用 mapStateToProps 映射了數據,并且使用 connect 方法包裹組件, this.state.counter 現在就變成了 this.props.counter ,然后這個值就是通過全局 store 獲取的,這個全局 store 將值傳遞給當前組件的 props 。(如果想知道具體,我在 React.js 模式 的文章中有介紹原理)。
import React from 'react'
import { connect } from 'react-redux'
import Actions from './Actions.js'
class App extends React.Component {
constructor(props) {
super(props)
this.addOne = this.addOne.bind(this)
}
addOne() {
this.props.dispatch(Actions.addOne())
}
render() {
return (
<div>
<button
onClick={ this.addOne }>
Increment
</button>
{ this.props.counter }
</div>
)
}
}
const mapStateToProps = store => {
return {
counter: store.counter
}
}
export default connect(mapStateToProps)(App)
現在當你點擊按鈕的時候,通過一個 action => dispatched 使全局 store 更新。然后這個數據通過外層組件傳遞到當前組件。
值得注意的是:當 props 被更新的時候,這也將觸發組件的 re-render(重新渲染)=> 這與你更新 state 的時候一樣。
& 應用場景
對于 Redux store 是很好的保持了除 UI 狀態數據外的應用狀態。有一個不錯的例子,比如用戶的登錄狀態。對于在在登錄狀態改變的同時,多數組件需要訪問這個數據信息做出響應。這些組件(至少有一個被渲染)將需要重新渲染與更新的信息。
Redux 觸發事件在你需要跨組件或者跨路由的情況下是很有用的。比如有一個登錄彈框,當你的 App 中有多個按鈕點擊都將觸發彈出它的時候。而不是在你需要渲染這個彈框的多個地方做判斷,你能通過一個頂層的 App 組件去使用 Redux action 去觸發它并且修改 store 的值。
總結:你想將跨組件的保持數據的時候能夠使用 store 。
三、this.<something>
在 React 的開發中,使用 this 來保存數據的場景很少。人們經常忘記了 React 使用的是 JavaScript 的 ES2015 的語法。 任何你能夠用 JavaScript 做的事情,你都能在 React 做 (這也是我非常喜歡 React 的原因呢 ^0^ freedom)。
下面的例子依然是一個計數應用,與之前的例子有點類似。
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.counter = 0
this.addOne = this.addOne.bind(this)
}
addOne() {
this.counter += 1
this.forceUpdate()
}
render() {
return (
<div>
<button
onClick={ this.addOne }>
Increment
</button>
{ this.counter }
</div>
)
}
}
export default App
我們是在組件中使用 this 存儲 counter 變量的值,并且使用 forceUpdate() 方法來觸發組件的重新渲染。 這是因為沒有任何的 state 和 props 修改,因此沒有自動觸發重新渲染機制。
這個例子實際上不應該使用 this 。如果你發現你必須使用 forceUpdate() 才能滿足需求,你可能代碼哪里出了問題。如果想值修改的時候自動觸發重新渲染,你應該使用 state 或者 props/Redux store (其實嚴格一點來說,作者這里的表述是不清的,其實重新渲染與 Redux 并無關系,本質上就是 props 的更新流入組件)。
& 應用場景
使用 this 來保存數據的時候,能夠在改變的時候不需要去觸發重新渲染的場景。比如:sockets 是很好的一個使用 this 保存數據的場景。
import React from 'react'
import { Socket } from 'phoenix'
class App extends React.Component {
componentDidMount() {
this.socket = new Socket('http://localhost:4000/socket')
this.socket.connect()
this.configureChannel("lobby")
}
componentWillUnmount() {
this.socket.leave()
}
configureChannel(room) {
this.channel = this.socket.channel(`rooms:${room}`)
this.channel.join()
.receive("ok", () => {
console.log(`Succesfully joined the ${room} chat room.`)
})
.receive("error", () => {
console.log(`Unable to join the ${room} chat room.`)
})
}
render() {
return (
<div>
My App
</div>
)
}
}
export default App
大多數人們沒有他們之前一直在使用 this 定義方法。放你定義 render() ,你實際上是: this.prototype.render = function () { } ,但是這在 ES2015 的 class 語法機制下是隱式的。
總結:在不需要數據改變的時候去觸發重新渲染機制的時候,能夠使用 this 去保存數據。
四、Static(靜態的方式)
靜態方法 和屬性也許是最少使用的(靜下來,我知道他們不是真正在 class 下的一個機制),大多數是因為他們沒有被頻繁使用。但是他們不是很復雜。如果你用過 PropTypes ,你就定義了一個 static 屬性。
下面有兩份代碼塊實際上是長得一樣的。人們經常使用的方式是第一種,第二種是你能使用 static 關鍵字來定義。
class App extends React.Component {
render() {
return (<div>{ this.props.title }</div>)
}
}
App.propTypes = {
title: React.PropTypes.string.isRequired
}
class App extends React.Component {
static propTypes {
title: React.PropTypes.string.isRequired
}
render() {
return (<div>{ this.props.title }</div>)
}
}
正如你看到的, static 并不復雜。他們是一種另外的方式去聲明一個類的值。這主要的不同是 static 不要像 this 那樣去實例化一個類再去訪問值。
class App extends React.Component {
constructor() {
super()
this.prototypeProperty = {
baz: "qux"
}
}
static staticProperty = {
foo: "bar"
};
render() {
return (<div>My App</div>)
}
}
const proto = new App();
const proto2 = proto.prototypeProperty // => { baz: "qux" }
const stat = App.staticProperty // => { foo: "bar" }
在這個例子中,你能看獲取 staticProperty 的值,我們只組要直接通過類名去訪問,而不是實例后。但是訪問 proto.prototypeProperty 就必須要 new App() .
& 應用場景
靜態方法和屬性是很少被使用,主要被用來定義工具方法或者特定類型的所有組件。
propTypes是一個工具例子,比如一個 Button 組件,每一個按鈕的渲染都需要相似的值。
另外個案例就是如果你關注獲取的數據。如果你是使用 GraphQL 或者 Falcor,你能具體描述需要服務端返回什么數據。這種方法你能不用接受大量組件不需要的數據。
class App extends React.Component {
static requiredData = [
"username",
"email",
"thumbnail_url"
]
render() {
return(<div></div>)
}
}
因此在例子中,在特殊組件請求數據之前,你能使用 App.requiredData 快速獲取必要的 query 的值。
總結:你也許幾乎不會使用 static 來保存數據。
五、其他方式...
其實是有另一個選擇,我沒有在標題中說明,因為你不應該這樣做:你可以存儲在一個全局變量,然后通過一個文件進行導出。
這是一個特別的場景,然后大多數情況下你不應該這樣做。
import React from 'react'
let counter = 0
class App extends React.Component {
constructor(props) {
super(props)
this.addOne = this.addOne.bind(this)
}
addOne() {
counter += 1
this.forceUpdate()
}
render() {
return (
<div>
<button
onClick={ this.addOne }>
Increment
</button>
{ counter }
</div>
)
}
}
export default App
你能夠看到和使用 this 是類似的,除了使用的是一個全局的變量。
如果你需要跨組件的共享數據以及保存一個全局變量,大多數更好的是使用 Redux store 。
總結:不要使用全局變量。
來自:https://segmentfault.com/a/1190000007186209