笔者最近在整理前段时间接手的其他团队的 RN 项目代码,在梳理项目中旧代码过程中,对 React 中 State Store Static This 产生疑惑,借此翻译这篇文章解惑,也分享给各位。

原文 https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00

发表时间 2016-08

作者 Sam Corcos

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 local state. The important thing to know about local state is that when a state value changes, it triggers a re-render.
This state can be passed down to children as props, which allows you to separate your components between smart data-components and dumb presentational-components if you chose.

React 刚刚面世之初,我们就注意到 local state。每当 state 值发生变化,都会触发组件重新 render,因此了解 state 是非常重要的。

当前组件的 state 会被当做 props 传递到子组件中,这个 props 允许你在数据组件和描绘型组件之间做出区分。

下面一个简单的使用 local state 计数 App 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 a controlled 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 from this.props.counter after being mapped from mapStateToProps in the connect 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 中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 global store is updated. The data is handled outside of our local component and is passed down.

It’s worth noting that when props are updated, it also triggers a re-render—just like when you update state.

当你点击按钮,与此链接的 action 就会被触发,同时全局的 store 就会更新。这样我们本地组件外层的数据就被操作并传递下去。

props 更新是没有副作用的,只有当你更新 state 时才会触发重新渲染。

Use cases

The Redux store 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 the store.

Takeaway: keep data that you intend to share across components in store.

Redux 中 store 应该维护应用的数据状态而不是 UI 的状态。用户登录数据状态就是另外一个例子。只要登录状态改变,项目中多数组件将需要访问这个登录信息,随着信息的更新,这些获取到信息更新的组件就都会重新 render。

Redux 通常也用于事件的触发,这些事件可能是横跨多个组件或者横跨多个路由。再以登录模块举例,可以在整个应用中来触发多个事件。你可以在应用的顶层,通过使用 Redux 对 store 进行修改,并使用 action 来触发条件渲染,而不是去不同地方去单独条件渲染。

Takeaway: 可以尝试在跨组件共享数据时,将数据保存进 store

this

One of the least utilized features when working with React is this. 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 中完成。

下面就是一个函数型计数应用,与上面两个例子相似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 the counter value in the component and using forceUpdate() to re-render when the value changes. This is because changes to anything other than state and props does not trigger a re-render.

This is actually an example of how you should not use this. If you find yourself using forceUpdate(), you’re probably doing something wrong. For values for which a change should trigger a re-render, you should use local state or props/Redux store.

我们在组件中存储 counter 的值,并且在这个值发生变化的时候使用 forceUpdate() 去重新渲染。这是由于没有 stateprops 发生变化,是不会触发组件的重新渲染。

这也是一个实际的非常糟糕地不使用 this 的例子。如果你发现你自己正在使用 forceUpdate() 你就有可能犯了一个错误。期望做到值改变而触发重新 render,就应该使用 local state 或者 props 或者是 Redux store

Use cases

The use case for this is to store values for which a change should not trigger a re-render. For example, sockets are a perfect thing to store on this.

举个例子,this 所存储的变量发生改变,但并不希望触发重新 render。比如,sockets 就和适合存储在 this 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 using this all the time in their function definitions. When you define render(), you’re really defining this.prototype.render = function(), but it’s hidden behind ES2015 class syntax.

Takeaway: use this 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 used PropTypes, you’ve already defined a static property.

The following two code blocks are identical. The first is how most people define PropTypes. The second is how you can define them with static.

Static methods 和 properties 可能是在 ES2015 类中最不为人知的一部分,主要是因为他们不太常用。然而他们并不难懂复杂。如果你已经用过 PropTypes, 那么你已经定义过 static 属性了。

下面这两段代码片段相同。第一段是大多数人如何定义 Proptypes。第二段是你可以使用 static 定义。

1
2
3
4
5
6
7
8
9
class App extends React.Component {
render() {
return (<div>{ this.props.title }</div>)
}
}

App.propTypes = {
title: React.PropTypes.string.isRequired
}
1
2
3
4
5
6
7
8
9
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 between static and this is that you do not need to instantiate the class to access the value.

你可以看到,static 并不复杂。他仅仅是给类增加值的另外一种方式。在 staticthis 之间主要的差异主要是不需要实例化来访问这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 the staticProperty value, we could just call it straight from the class without instantiating it, but to get prototypeProperty, we had to instantiate it with new 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.

PropTypes are 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,那么你可以从服务端区分想要的数据。这种方式你不需要在获取组件多余的数据。

1
2
3
4
5
6
7
8
9
10
11
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 with App.requiredData. This allows you to make a request without over-fetching.

Takeaway: you’re probably never going to use static.

在上面实例组件中,在具体得组件获取数据之前,你可以快速地通过 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-scoped variable.

There are specific situations in which it makes sense, but for the most part you just shouldn’t do it.

还有另外一个选择,我故意省略了标题,因为你应该谨慎使用它:你可以储存在模块作用域变量中。

这是一种行之有效的特殊方法,但是最好不要使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 using this, 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 your store, otherwise using a static 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 Redux store.

Takeaway: don’t use module-scoped variables if you can avoid it.

从上面的代码你可以看到与使用 this 很相似,尤其是我们将值存储在了组件之外,如果每个文件有多个组件极有可能产生问题。如果你没有将这个值绑定在 store 上,那你可能很希望使用 this 去设定初始默认值,否则使用 static 来设置默认 props 会更好。

如果你需要跨组件之间共享数据,并且希望将这些数据维持在每一个模块都有效,那么使用 Redux store 会更好。

Takeaway: 如果可以避免的话,不要使用模块作用域的变量。