原文链接: Where to Hold React Component Data: state, store, static, and this

随着ReactRedux的出现,出现了一些常见问题:

在Redux的store中我该存些什么,在本地化存储中又该存哪些东西呢?

但这个问题确实是太简单了,因为在组件中还有其他两种方式用以存储数据:staticthis

我们来重温一下它们中的每一个,什么时候去用它们。

本地的state

当React初次被介绍时,呈现给我们的是本地的state。需要注意的一点就是当本地的state值发生改变时,它会触发一次渲染。

这个state可以把一些能够将你的组件从智能数据组件和纯展示的props传递给子组件

以下是使用本地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>
)
}
}
export default App

你的数据(counter的值)存储在了App这个组件中,并且能够被传入它的子组件中。

用例

假定这个计数器对你的应用很重要,并且存储的数据对其他组件可能也会很有用,因此你不应该使用本地的state来存储这些值。

目前最佳实践是使用本地state来处理你的用户接口(UI)状态而非数据。例如,使用一个受控组件来填写一个表单就是一个本地state的完美有效使用。

另一个你可以使用本地state来存储UI数据的例子就是在列表选项中选中tab标签。

考虑什么时候才使用本地的state可以先衡量一下你现在存储的值是否会被其他组件使用到是一个不错的途径。如果一个值只会被一个单独的组件(或者说只有一个子组件的组件)使用到,那么使用本地state存储将会更加安全。

小结: 在本地state中存储UI状态以及临时数据(例如表单的输入)。

Redux 存储

一段时间后,每个人都开始推崇单向数据流的理念,我们有了Redux。

使用Redux, 我们又一个全聚德store。这个store的生命周期在你的应用中级别最高并且能够向每一个子组件传递数据。你可以使用connectmapStateToProps来在某个组件中连接全局的store

首先,人们把所有东西都存入Redux的store中。用户,实体,表单,套接字… 所有你说的上来的。(这几个词可译可不译 ——译者注)

以下是一个相同但使用Redux的计数器应用。需要注意的很重要的一点是counter现在在connect函数(用来将counter的值从全局的store传递到当前组件的props中)中使用mapStateToProps后是来自于this.props.counter

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会被分发并缺全局的store会被更新。数据在我们本地组件之外被处理并回传了进来。

props被更新是没有什么卵用的,它也可以像更新state一样触发二次渲染。

用例

Redux 的store用来存储应用状态是非常好的而非UI状态。一个完美的例子是用户的登录状态。我们的很多组件都会触及这个信息,登录状态一改变,所有的组件(至少是那些被渲染出来的) 将会依据更新的信息被重新渲染。

Redux在触发一些在多组件或者多路由之间的事件上很有用。这样的例子如可能被贯穿于整个应用的大量按钮所触发的登录遮罩。不要根据条件去很多地方渲染遮罩,你可以在你应用的顶层容器里通过Redux根据store中的某个值的改变来触发。

小结: 将你将在多个组件之间共享的数据存入store

this.

在使用React时,小而有用的一个特性是this。人们经常忘记Ract使用的是ES2015语法。你在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>
)
}
}
export default App

我们把counter值存在了组件中并且当它改变后调用forceUpdate() 来重新渲染。这是因为除了stateprops以外的任何东西都不会触发二次渲染。

这也是不应该使用this的一个例子。如果你发现你在使用forceUpdate,你可能在做一些错误的事。对于一些应该触发二次渲染的值,你应该使用state或者Redux store后的props。

用例

this的用例是用来存储一些不会因为改变触发二次渲染的值。例如,使用this来存储套接字(sockets)就很完美。

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语法中被隐藏掉了。

小结: 使用this去存储那些不会触发二次渲染的东西

Static

静态方法 和属性是ES2015中classes(要冷静,是的,我知道在底层它并不是真正的类)鲜为人知的一点。更多是因为它们的使用频率不高。但他们并非是一堆复杂的东西。如果你已经在使用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>)
}
}

正如你所看到的,static并不复杂。这仅仅是给class添加一个值的方式,区别于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值,我们直接从class中获取到了而没有实例化它,但在获取prototypeProperty时,我们需要使用new App()来实例化。

用例

静态方法和属性比较少用,应该被用于一些特定类型的组件才会需要用到的一些工具类函数。

PropTypes是一种你可能会关联一些按钮类的组件时用到的工具类函数例子,因为你渲染的每一个按钮都会使用一些相同的值。

另外一个用例就是如果你担心过度请求数据的问题。如果你使用GraphQL或者Falcor,你能够确定从服务器返回的数据。这样你不需要去终止请求超出你组件所需要的一些数据。

class App extends React.Component {
static requiredData = [
"username",
"email",
"thumbnail_url"
]
render() {
return(<div></div>)
}
}

在以上例子中,在为一个指定的组件请求数据之前,你可以通过App.requiredData来快速的获取查询所需要值的数组。浙江使得你不需要过度请求数据就可以发起一个请求。

小结: 你可能永远也不会用到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

你可以看到除了污染了全局state,它的用法和this是一样的。

如果你需要和其他组件共用一些数据并且想要将数据全局保存,那么使用Redux store是极好的。

小结: 不要使用全局变量。

donation