如何存储 React 组件的数据

BobbyeTread 4年前
   <p>主要讲解这几个部分:state、store、static、this、module-global data</p>    <h3><strong>前言</strong></h3>    <p>随着 React 和 Redux 的到来,一个共同的问题被问到:</p>    <p>我应该将数据保存在 Redux Store 中呢?还是应该保存成本地 state?</p>    <p>其实这个问题是说的不完整( 或者原文说的太简单 ),因为你能在组件中存储数据的方式还有两种:static 和 this.(其实就是静态数据,还是类的实例数据)</p>    <p>让我们来依次讨论一下,你应该在什么时候使用它们。</p>    <h3><strong>一、本地 state ( Local state )</strong></h3>    <p>ps:下面翻译的时候将 local state => 直接翻译成 state 吧,对于 state 其实的作用域是与组件挂钩的,直接翻译成本地 state 其实是不准确的。</p>    <p>当 React 在第一次被介绍的时候,我们就知道 <strong>state</strong> 。我们知道一个很重要的事情,就是当 <strong>state</strong> 的值改变的时候将触发组件的 re-render(重新渲染)。</p>    <p>这个 state 也能传递给子组件,子组件中通过 <strong>props</strong> 来获取传递的数据,这就允许你能够将你的组件分为:smart data-components(智能数据组件)and dumb presentational-components (填鸭式组件)。</p>    <p>这里有一个使用 <strong>state</strong> 来编写的 counter app(计数 APP):</p>    <pre>  <code class="language-javascript">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</code></pre>    <p>你的数据( counter 的值 )是存储在 App 组件中,也能向下传递给子组件。</p>    <h3><strong>& 应用场景</strong></h3>    <p>假如 <strong>counter</strong> 数据在你的 app 中是很重要的,以及这个数据会存储下来用于其他组件,那么你将不希望使用 <strong>state</strong> 来保存这个值。</p>    <p>这最好的实践是处理用户接口 (UI, User Interface) 的状态数据。比如:使用一个 <a href="/misc/goto?guid=4959721076852805141" rel="nofollow,noindex">交互组件</a> 去填写表单,这时候使用 <strong>state</strong> 是不错的选择。</p>    <p>另外的例子,就是在 UI 交互相关的数据中使用,比如你将在一个列表中记录当前选中的 tab (选项卡)的状态,你就能存储一个 <strong>state</strong> 。</p>    <p>在你选择 <strong>state</strong> 的使用时机,也能够这样考虑:你存储的数据是否会在其他组件中使用。如果一个值是只有一个组件(或者也许只有一个子组件),这时使用 <strong>state</strong> 来保持这个值(或者说状态)都是安全的。</p>    <p>总结:保持 UI 状态和暂时的数据(比如:表单的输入),能够使用 <strong>state</strong> 。</p>    <h3><strong>二、Redux store</strong></h3>    <p>随着发展,每个人开始选择 <a href="/misc/goto?guid=4959642351070558098" rel="nofollow,noindex">单向数据流</a> , 我们选择 Redux。</p>    <p>对于 Redux,我们将获得一个全局的 <strong>store</strong> 。这个 store 将绑定在一个最高等级的组件中,然后将 App 的数据流入所有的子组件(其实整个 App 就已经是这个最高等级组件的子组件了)。你能 connect 全局 <strong>store</strong> ,使用: <a href="/misc/goto?guid=4959671501474108319" rel="nofollow,noindex">connect wrap 和 mapStateToProps 方法</a> .</p>    <p>首先,人们就将任何事情都交给了 Redux <strong>store</strong> 。比如:Users, modals, forms, sockets...,主要你知道的。</p>    <p>下面是一个和之前相同的计数 App,然后使用了 Redux。主要需要注意的是 <strong>counter</strong> 数据,使用 <strong>mapStateToProps</strong> 映射了数据,并且使用 <strong>connect</strong> 方法包裹组件, <strong>this.state.counter</strong> 现在就变成了 <strong>this.props.counter</strong> ,然后这个值就是通过全局 <strong>store</strong> 获取的,这个全局 <strong>store</strong> 将值传递给当前组件的 <strong>props</strong> 。(如果想知道具体,我在 <a href="/misc/goto?guid=4959721077008854478" rel="nofollow,noindex">React.js 模式</a> 的文章中有介绍原理)。</p>    <pre>  <code class="language-javascript">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)</code></pre>    <p>现在当你点击按钮的时候,通过一个 action => dispatched 使全局 <strong>store</strong> 更新。然后这个数据通过外层组件传递到当前组件。</p>    <p>值得注意的是:当 <strong>props</strong> 被更新的时候,这也将触发组件的 re-render(重新渲染)=> 这与你更新 <strong>state</strong> 的时候一样。</p>    <h3><strong>& 应用场景</strong></h3>    <p>对于 Redux <strong>store</strong> 是很好的保持了除 UI 状态数据外的应用状态。有一个不错的例子,比如用户的登录状态。对于在在登录状态改变的同时,多数组件需要访问这个数据信息做出响应。这些组件(至少有一个被渲染)将需要重新渲染与更新的信息。</p>    <p>Redux 触发事件在你需要跨组件或者跨路由的情况下是很有用的。比如有一个登录弹框,当你的 App 中有多个按钮点击都将触发弹出它的时候。而不是在你需要渲染这个弹框的多个地方做判断,你能通过一个顶层的 App 组件去使用 Redux action 去触发它并且修改 <strong>store</strong> 的值。</p>    <p>总结:你想将跨组件的保持数据的时候能够使用 <strong>store</strong> 。</p>    <h3><strong>三、this.<something></strong></h3>    <p>在 React 的开发中,使用 <strong>this</strong> 来保存数据的场景很少。人们经常忘记了 React 使用的是 JavaScript 的 ES2015 的语法。 任何你能够用 JavaScript 做的事情,你都能在 React 做 (这也是我非常喜欢 React 的原因呢 ^0^ freedom)。</p>    <p>下面的例子依然是一个计数应用,与之前的例子有点类似。</p>    <pre>  <code class="language-javascript">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</code></pre>    <p>我们是在组件中使用 <strong>this</strong> 存储 <strong>counter</strong> 变量的值,并且使用 forceUpdate() 方法来触发组件的重新渲染。 <em>这是因为没有任何的 state 和 props 修改,因此没有自动触发重新渲染机制。</em></p>    <p>这个例子实际上不应该使用 <strong>this</strong> 。如果你发现你必须使用 <strong>forceUpdate()</strong> 才能满足需求,你可能代码哪里出了问题。如果想值修改的时候自动触发重新渲染,你应该使用 <strong>state</strong> 或者 <strong>props/Redux store</strong> (其实严格一点来说,作者这里的表述是不清的,其实重新渲染与 Redux 并无关系,本质上就是 props 的更新流入组件)。</p>    <h3><strong>& 应用场景</strong></h3>    <p>使用 <strong>this</strong> 来保存数据的时候,能够在改变的时候不需要去触发重新渲染的场景。比如:sockets 是很好的一个使用 <strong>this</strong> 保存数据的场景。</p>    <pre>  <code class="language-javascript">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</code></pre>    <p>大多数人们没有他们之前一直在使用 <strong>this</strong> 定义方法。放你定义 <strong>render()</strong> ,你实际上是: <strong>this.prototype.render = function () { }</strong> ,但是这在 ES2015 的 class 语法机制下是隐式的。</p>    <p>总结:在不需要数据改变的时候去触发重新渲染机制的时候,能够使用 <strong>this</strong> 去保存数据。</p>    <h3><strong>四、Static(静态的方式)</strong></h3>    <p>静态方法 和属性也许是最少使用的(静下来,我知道他们不是真正在 class 下的一个机制),大多数是因为他们没有被频繁使用。但是他们不是很复杂。如果你用过 PropTypes ,你就定义了一个 <strong>static</strong> 属性。</p>    <p>下面有两份代码块实际上是长得一样的。人们经常使用的方式是第一种,第二种是你能使用 <strong>static</strong> 关键字来定义。</p>    <pre>  <code class="language-javascript">class App extends React.Component {    render() {      return (<div>{ this.props.title }</div>)    }  }    App.propTypes = {    title: React.PropTypes.string.isRequired  }</code></pre>    <pre>  <code class="language-javascript">class App extends React.Component {    static propTypes {      title: React.PropTypes.string.isRequired    }        render() {      return (<div>{ this.props.title }</div>)    }  }</code></pre>    <p>正如你看到的, <strong>static</strong> 并不复杂。他们是一种另外的方式去声明一个类的值。这主要的不同是 <strong>static</strong> 不要像 <strong>this</strong> 那样去实例化一个类再去访问值。</p>    <pre>  <code class="language-javascript">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" }</code></pre>    <p>在这个例子中,你能看获取 <strong>staticProperty</strong> 的值,我们只组要直接通过类名去访问,而不是实例后。但是访问 <strong>proto.prototypeProperty</strong> 就必须要 <strong>new App()</strong> .</p>    <h3><strong>& 应用场景</strong></h3>    <p>静态方法和属性是很少被使用,主要被用来定义工具方法或者特定类型的所有组件。</p>    <p>propTypes是一个工具例子,比如一个 Button 组件,每一个按钮的渲染都需要相似的值。</p>    <p>另外个案例就是如果你关注获取的数据。如果你是使用 GraphQL 或者 Falcor,你能具体描述需要服务端返回什么数据。这种方法你能不用接受大量组件不需要的数据。</p>    <pre>  <code class="language-javascript">class App extends React.Component {    static requiredData = [      "username",      "email",      "thumbnail_url"    ]        render() {      return(<div></div>)    }  }</code></pre>    <p>因此在例子中,在特殊组件请求数据之前,你能使用 <strong>App.requiredData</strong> 快速获取必要的 query 的值。</p>    <p>总结:你也许几乎不会使用 <strong>static</strong> 来保存数据。</p>    <h3><strong>五、其他方式...</strong></h3>    <p>其实是有另一个选择,我没有在标题中说明,因为你不应该这样做:你可以存储在一个全局变量,然后通过一个文件进行导出。</p>    <p>这是一个特别的场景,然后大多数情况下你不应该这样做。</p>    <pre>  <code class="language-javascript">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</code></pre>    <p>你能够看到和使用 <strong>this</strong> 是类似的,除了使用的是一个全局的变量。</p>    <p>如果你需要跨组件的共享数据以及保存一个全局变量,大多数更好的是使用 Redux <strong>store</strong> 。</p>    <p>总结:不要使用全局变量。</p>    <p> </p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007186209</p>    <p> </p>