如何在同一个反应组件中使用本地状态和 redux 存储状态?

Posted

技术标签:

【中文标题】如何在同一个反应组件中使用本地状态和 redux 存储状态?【英文标题】:How do I use local state along with redux store state in the same react component? 【发布时间】:2017-02-20 20:46:48 【问题描述】:

我有一个显示联系人的表格,我想按名字对联系人进行排序。联系人数组来自 redux 存储,然后将通过道具来,但我希望本地状态保存这些联系人的排序方式,因为它是本地 UI 状态。我如何实现这一目标?到目前为止,我已将联系人放入componentWillReceiveProps,但由于某种原因,它在更改时没有收到道具。每次 redux 存储状态发生变化时如何更新本地状态?

const Table = React.createClass(
  getInitialState () 
    return contacts: []
  ,
  componentWillReceiveProps () 
    this.setState( contacts: this.props.data.contacts)
  ,
  sortContacts (parameter, e)
    ...
  ,
  render () 
    return (
      <table>
        <thead>
          <tr>
            <th onClick=this.sortContacts.bind(this, "firstName")>First Name</th>
          </tr>
        </thead>
        <tbody>
          contactRows
        </tbody>
      </table>
    )
  
)

更新当前包含过滤的代码

import React, Component from 'react'
import TableRow from './TableRow'

class Table extends Component 
  constructor (props) 
    super(props)
    this.state =  sortBy: "fistName" 
  
  sortContacts (parameter) 
    console.log('in sortContacts')

    this.setState( sortBy: parameter )
  
  sortedContacts () 
    console.log('in sortedContacts')

    const param = this.state.sortBy
    return (
      this.props.data.contacts.sort(function (a, b)
        if (!a.hasOwnProperty(param))
          a[param] = " ";
        
        if (!b.hasOwnProperty(param))
          b[param] = " ";
        
        const nameA = a[param].toLowerCase(), nameB = b[param].toLowerCase();
        if (nameA > nameB) 
          return 1;
         else 
          return -1;
        
      )
    )
  
  filteredSortedContacts () 
    console.log('in filteredSortedContacts')

    const filterText = this.props.data.filterText.toLowerCase()
    let filteredContacts = this.sortedContacts()
    if (filterText.length > 0) 
      filteredContacts = filteredContacts.filter(function (contact)
        return (
          contact.hasOwnProperty('lastName') &&
          contact.lastName.toLowerCase().includes(filterText)
        )
      )
    
    return filteredContacts
  
  contactRows () 
    console.log('in contactRows')
    return this.filteredSortedContacts().map((contact, idx) =>
      <TableRow contact=contact key=idx/>
    )
  
  render () 
    return (
      <div className="table-container">
        <table className="table table-bordered">
          <thead>
            <tr>
              <th className="th-cell" onClick=this.sortContacts.bind(this, "firstName")>First Name</th>
              <th onClick=this.sortContacts.bind(this, "lastName")>Last Name</th>
              <th>Date of Birth</th>
              <th>Phone</th>
              <th>Email</th>
              <th>Notes</th>
            </tr>
          </thead>
          <tbody>
            this.contactRows()
          </tbody>
        </table>
      </div>
    )
  


export default Table

我现在看到的问题是 contactRows, filteredSortedContacts, sortedContacts 被多次调用,每个 TableRow 调用一次。如果我只在正文中调用一次contactRows,我看不出这会发生什么。

【问题讨论】:

显示reducer和sortContants的内容。 【参考方案1】:

您同时使用 redux 存储和本地存储的方法是正确的。

只是不要尝试在你的组件中复制来自 redux 存储的状态。继续通过 props 引用它。

改为创建一个sortedContacts 函数,通过将本地存储的sortBy 参数应用于redux 存储的联系人来动态计算值。

const Table extends React.Component 
  constructor(props) 
    super(props);
    this.state = 
      sortBy: 'id' // default sort param
    
  

  sortContacts(param) 
    this.setState( sortBy: param)
  

  sortedContacts() 
    return this.props.contacts.sort(...); // return sorted collection
  

  render() 
    return (
      <table>
        <thead>
          <tr>
            <th onClick=() => this.sortContacts("firstName")>First Name</th>
          </tr>
        </thead>
        <tbody>
          this.sortedContacts()
        </tbody>
      </table>
    )
  

【讨论】:

哇,这太酷了!因此,不是让本地状态保存一个排序的联系人数组,而是让它保存进行排序所需的参数。当此参数更改时,是否仍会导致自动重新渲染,进而再次调用#sortedContacts?我认为自动重新渲染仅在状态更改并且状态中有需要渲染的内容时发生,而不是依赖于状态更改的方法。 为什么这样做比复制 redux 状态并对其进行排序更好? 因为如果您只有一个事实来源,您就不需要任何同步代码。在您的情况下,您不仅可以轻松地应用排序,还可以以相同的方式应用过滤、转换等。 你能看看我的更新,看看你是否知道为什么我要多次运行排序方法? @MichałSzajbe 关于一个事实来源的好观点。我唯一担心的是如果我有 1000 行 - 这种方法不会在每次组件呈现时进行排序吗?说我有分页,每次我点击next,它都会使用1000个数据CMIIW。有更好的方法吗?【参考方案2】:

初始渲染不调用componentWillReceiveProps() 方法。如果您只打算将 props 中的数据用作初始数据,可以这样做:

getInitialState () 
  return 
    contacts: this.props.data.contacts
  

在React docs 中,他们建议您将道具命名为initialContacts,以明确道具的唯一目的是在内部初始化某些东西。

现在,如果您希望它在 this.props.contacts 更改时更新,您可以像以前一样使用 componentWillReceiveProps()。但我不确定这是不是最好的主意。来自文档:

在 getInitialState 中使用 props 生成状态通常会导致 “真相来源”的重复,即真实数据在哪里。这是 因为 getInitialState 仅在组件第一次调用时调用 已创建。

尽可能动态计算值,以确保它们不会 稍后会失去同步并导致维护问题。

【讨论】:

【参考方案3】:

React 16.3 包含一个静态函数getDerivedStateFromProps(nextProps, prevState),它将在初始挂载后以及接收到新道具时调用。见https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

【讨论】:

它并不总是在 prop 更改时触发,如果 prop 不调用渲染它不会被触发。这是因为它在调用的渲染方法之前被调用,而不是在技术上初始挂载之后。

以上是关于如何在同一个反应组件中使用本地状态和 redux 存储状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用从子组件传递给组件的反应变量更新 redux 状态

如何在反应中使用redux中的布尔状态值渲染组件?

从 redux 更改状态后如何以及何时调用反应组件方法

如何在 React/Redux 中本地管理组件的状态

使用 redux 初始化全局状态,然后让本地组件状态接管

使用 get API 调用反应全局状态/道具(没有 Redux)