【譯】React 中 State, Store, Static, This 的幾個問題
筆者最近在整理前段時間接手的其他團隊的 RN 專案程式碼,在梳理專案中舊程式碼過程中,對 React 中 State Store Static This 產生疑惑,藉此翻譯這篇文章解惑,也分享給各位。
原文ofollow,noindex">https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00
發表時間 2016-08
Where to Hold React Component Data: state, store, static, and this
With the advent of React and Redux, a common question has emerged:
What should I hold in the Redux store, and what should I save in local state?
在開發 React 和 Redux 專案時,經常會被問到一個問題?
我應該把什麼維護在 Redux Store 中?我該在 Local state 中儲存什麼?
But this question is actually too simplistic, because there are also two other ways you can store data for use in a component: static and this. Let’s go over what each of these, and when you should use them.
然而問題非常簡單,因為在 component 中你可以使用兩種其他的方式儲存資料:static 和 this。
讓我們一起來詳細瞭解下如何使用。
Local state 元件的本地狀態
When React was first introduced, we were presented with localstate . The important thing to know about localstate is that when astate value changes, it triggers a re-render.
This state can be passed down to children asprops , which allows you to separate your components between smart data-components and dumb presentational-components if you chose.
React 剛剛面世之初,我們就注意到 localstate 。每當state 值發生變化,都會觸發元件重新 render,因此瞭解state 是非常重要的。
當前元件的 state 會被當做props 傳遞到子元件中,這個 props 允許你在資料元件和描繪型元件之間做出區分。
下面一個簡單的使用 local state 計數 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> ) } }
Your data (the value of the counter) is stored within the App component, and can be passed down its children.
App 元件中資料被儲存其中,並可以向子元件進行傳遞。
Use cases
Assuming your counter is important to your app, and is storing data that would be useful to other components, you would not want to use local state to keep this value.
The current best practice is to use local state to handle the state of your user interface (UI) state rather than data. For example, using acontrolled component to fill out a form is a perfectly valid use of local state.
Another example of UI data that you could store in local state would be the currently selected tab from a list of options.
假設計數器 Couter 對於 App 很重要,並且它正在儲存其他元件的重要資料,你不會希望使用 local state 來儲存資料的。
目前最佳實踐是使用 local state 來處理 UI 的狀態,不是使用資料。比如,使用Controlled Components 去實現一個 form 表單時使用 local state 是非常合理的。
UI 資料的另外一個例子,可以在 local state 中儲存備選 options 列表中已選中的選項。
A good way to think about when to use local state is to consider whether the value you’re storing will be used by another component. If a value is specific to only a single component (or perhaps a single child of that component), then it’s safe to keep that value in local state.
Takeaway: keep UI state and transitory data (such as form inputs) in local state.
思考何時使用 local state 一個好方法,是考慮兼顧你正在儲存的值是否會被另外一個元件使用到。如果這個值非常明確地只在單一元件(或單一子元件)中出現,那麼將它儲存在 local state 中就是非常安全的做法。
Takeaway:可以將 UI 狀態和臨時資料(form 表單輸入資料)儲存在 local state。
Redux store
Then after some time had elapsed and everyone started getting comfortable with the idea of unidirectional data flow, we got Redux.
With Redux, we get a global store. This store lives at the highest level of your app and passes data down to all children. You connect to the global store with the connect wrapper and a mapStateToProps function.
隨著時間流逝,大家都在習慣這種單向資料流的思想,隨著出現了 Redux。
在 Redux 中,我們有一個全域性的 store,它在 App 中處於最高層級,可以將資料傳遞到所有子元件中。你可以使用 connect 和 mapStateToProps 方法將全域性 store 和你的元件連結起來已獲取資料。
At first, people put everything in the Redux store. Users, modals, forms, sockets… you name it.
Below is the same counter app, but using Redux. The important thing to note is that counter now comes fromthis.props.counter after being mapped frommapStateToProps in theconnect function, which takes the counter value from the global store and maps it to the current component’s props.
期初,人們把所有的東西都塞進 Redux store 中。
下面是剛剛那個計數器,區別是使用了 Redux。要點是計數器在通過connect 方法mapStateToProps 對映之後獲取this.props.counter ,這個值是從全域性 store 中獲取到並對映到當前元件的 props 中的。
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)
Now when you click on the button, an action is dispatched and the globalstore is updated. The data is handled outside of our local component and is passed down.
It’s worth noting that whenprops are updated, it also triggers a re-render—just like when you updatestate .
當你點選按鈕,與此連結的 action 就會被觸發,同時全域性的store 就會更新。這樣我們本地元件外層的資料就被操作並傳遞下去。
props更新是沒有副作用的,只有當你更新state 時才會觸發重新渲染。
Use cases
The Reduxstore is great for keeping application state rather than UI state. A perfect example is a user’s login status. Many of your components will need access to this information, and as soon as the login status changes, all of those components (the ones that are rendered, at least) will need to be re-rendered with the updated information.
Redux is also useful for triggering events for which you need access on multiple components or across multiple routes. An example of this would be a login modal, which can be triggered by a multitude of buttons all across your app. Rather than conditionally rendering a modal in a dozen places, you can conditionally render it at the top-level of your app and use a Redux action to trigger it by changing a value in thestore .
Takeaway: keep data that you intend to share across components instore .
Redux 中store 應該維護應用的資料狀態而不是 UI 的狀態。使用者登入資料狀態就是另外一個例子。只要登入狀態改變,專案中多陣列件將需要訪問這個登入資訊,隨著資訊的更新,這些獲取到資訊更新的元件就都會重新 render。
Redux 通常也用於事件的觸發,這些事件可能是橫跨多個元件或者橫跨多個路由。再以登入模組舉例,可以在整個應用中來觸發多個事件。你可以在應用的頂層,通過使用 Redux 對store 進行修改,並使用 action 來觸發條件渲染,而不是去不同地方去單獨條件渲染。
Takeaway: 可以嘗試在跨元件共享資料時,將資料儲存進store 。
this
One of the least utilized features when working with React isthis . People often forget that React is just JavaScript with ES2015 syntax. Anything you can do in JavaScript, you can also do in React.
The example below is a functional counter app, similar to the two examples above.
在 React 眾多特性中this 就是其中之一。大家通常忘記一件事,就是 React 恰恰是使用 ES2015 語法的 Javascript 實現的。任何在 Javascript 可以完成的事情,同樣可以放在 React 中完成。
下面就是一個函式型計數應用,與上面兩個例子相似。
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> ) } }
We’re storing thecounter value in the component and usingforceUpdate() to re-render when the value changes. This is because changes to anything other thanstate andprops does not trigger a re-render.
This is actually an example of how you should not usethis . If you find yourself usingforceUpdate() , you’re probably doing something wrong. For values for which a change should trigger a re-render, you should use localstate orprops /Reduxstore .
我們在元件中儲存counter 的值,並且在這個值發生變化的時候使用forceUpdate() 去重新渲染。這是由於沒有state 和props 發生變化,是不會觸發元件的重新渲染。
這也是一個實際的非常糟糕地不使用this 的例子。如果你發現你自己正在使用forceUpdate() 你就有可能犯了一個錯誤。期望做到值改變而觸發重新 render,就應該使用 localstate 或者props 或者是 Reduxstore 。
Use cases
The use case forthis is to store values for which a change should not trigger a re-render. For example, sockets are a perfect thing to store onthis .
舉個例子,this 所儲存的變數發生改變,但並不希望觸發重新 render。比如,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
Also, many people don’t realize they’re already usingthis all the time in their function definitions. When you definerender() , you’re really definingthis.prototype.render = function() , but it’s hidden behind ES2015 class syntax.
Takeaway: usethis to store things that shouldn’t trigger a re-render.
同時,很多人並沒有意識到在定義 function 時已經一直在使用this 。當你定義render() 時,實際上是在定義this.prototype.render = function() ,但是它是 ES2015 類定義語法的隱藏式的寫法。
Takeaway: 使用this 儲存變數不應該觸發重新 render。
Static
Static methods and properties are perhaps the least known aspect of ES2015 classes (calm down, yes, I know they aren’t really classes under the hood), mostly because they aren’t used all that frequently. But they actually aren’t especially complicated. If you’ve usedPropTypes, you’ve already defined astatic property.
The following two code blocks are identical. The first is how most people define PropTypes. The second is how you can define them withstatic .
Static methods 和 properties 可能是在 ES2015 類中最不為人知的一部分,主要是因為他們不太常用。然而他們並不難懂複雜。如果你已經用過PropTypes, 那麼你已經定義過static 屬性了。
下面這兩段程式碼片段相同。第一段是大多數人如何定義 Proptypes。第二段是你可以使用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>) } }
As you can see,static is not all that complicated. It’s just another way to assign a value to a class. The main difference betweenstatic andthis is that you do not need to instantiate the class to access the value.
你可以看到,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" }
In the example above, you can see that to get thestaticProperty value, we could just call it straight from the class without instantiating it, but to get prototypeProperty, we had to instantiate it withnew App() .
在上面的例子中,你可以看到獲取staticProperty 靜態屬性的值,我們僅僅呼叫 class 中的靜態方法即可,並不需要例項化,但是如果想要獲取 prototypeProperty 屬性,我們就不得不使用new App() 例項化以後才可以訪問到。
Use cases
Static methods and properties are rarely used, and should be used for utility functions that all components of a particular type would need.
PropTypesare an example of a utility function where you would attach to something like a Button component, since every button you render will need those same values.
Another use case is if you’re concerned about over-fetching data. If you’re using GraphQL or Falcor, you can specify which data you want back from your server. This way you don’t end up receiving a lot more data than you actually need for your component.
靜態方法和屬性很少使用,應作為元件中的工具函式來使用。
PropTypes就是工具函式的例子,當建立按鈕元件等其他類似元件時,儘管渲染出來的每一個按鈕仍然需要相同的值。
另一個應用例子就是,如果你考慮從遠端 fetch 資料。如果你正使用 GraphQL 或者 Falcor,那麼你可以從服務端區分想要的資料。這種方式你不需要在獲取元件多餘的資料。
class App extends React.Component { static requiredData = [ "username", "email", "thumbnail_url" ] render() { return(<div></div>) } }
So in the example component above, before requesting the data for a particular component, you could quickly get an array of required values for your query withApp.requiredData . This allows you to make a request without over-fetching.
Takeaway: you’re probably never going to usestatic .
在上面例項元件中,在具體得元件獲取資料之前,你可以快速地通過App.requiredData 來獲取這個資料的陣列。這允許你不用 over-fetching 就可以完成請求。
Takeaway: 你可能永遠不會用到static 。
That other option…
There is actually another option, which I intentionally left out of the title because you should use it sparingly: you can store things in a module-scopedvariable .
There are specific situations in which it makes sense, but for the most part you just shouldn’t do it.
還有另外一個選擇,我故意省略了標題,因為你應該謹慎使用它:你可以儲存在模組作用域變數中。
這是一種行之有效的特殊方法,但是最好不要使用。
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
You can see this is almost the same as usingthis , except that we’re storing the value outside of our component, which could cause problems if you have more than one component per file. You might want to use this for setting default values if the values are not tied to yourstore , otherwise using astatic for default props would be better.
If you need to share data across components and want to keep data available to everything the module, it’s almost always better to use your Reduxstore .
Takeaway: don’t use module-scoped variables if you can avoid it.
從上面的程式碼你可以看到與使用this 很相似,尤其是我們將值儲存在了元件之外,如果每個檔案有多個元件極有可能產生問題。如果你沒有將這個值繫結在store 上,那你可能很希望使用 this 去設定初始預設值,否則使用static 來設定預設 props 會更好。
如果你需要跨元件之間共享資料,並且希望將這些資料維持在每一個模組都有效,那麼使用 Reduxstore 會更好。
Takeaway: 如果可以避免的話,不要使用模組作用域的變數。