这是 redux 入门系列的第三部分,在本教程中,我们将学习如何将 Redux 存储与 react 连接起来。Redux是一个独立的库,可与所有流行的前端库和框架协同工作。由于它的功能性方法,它可以与 React 完美配合。
您无需遵循本系列的前面部分即可使本教程有意义。如果您在这里学习如何使用 React 和 Redux,您可以阅读下面的快速回顾,然后查看上一部分的代码并从那里开始。
快速回顾
在第一篇文章中,我们了解了 Redux 工作流程并回答了这个问题,为什么是 Redux? 我们创建了一个非常基本的演示应用程序,并向您展示了 Redux 的各种组件——actions、reducers 和 store——是如何连接的。
在上一篇文章中,我们开始构建一个联系人列表应用程序,该应用程序允许您添加联系人,然后将它们显示为列表。我们为我们的联系人列表创建了一个 Redux 存储,并添加了一些减速器和操作。我们尝试使用 和 之类的存储方法来分派操作并检索新store.dispatch()
状态store.getState()
。
在本文结束时,您将了解到:
容器组件和展示组件的区别
关于 react-redux 库
如何使用绑定 react 和 redux
connect()
如何使用
mapDispatchToProps
如何使用检索状态
mapStateToProps
本教程的代码可在 GitHub 上的 react-redux-demo存储库中找到。从 v2 分支获取代码并将其用作本教程的起点。如果您想知道本教程结束时应用程序的外观,请尝试 v3 分支。让我们开始吧。
设计组件层次结构:智能组件与愚蠢组件
这是您可能以前听说过的概念,但让我们快速了解一下智能组件和哑组件之间的区别。回想一下,我们为组件创建了两个单独的目录,一个名为containers/ ,另一个名为components/。这种方法的好处是行为逻辑与视图分离。
表示组件被认为是愚蠢的,因为它们关心事物的外观。它们与应用程序的业务逻辑解耦,专门通过 props 从父组件接收数据和回调。如果数据来自父组件的本地状态,他们不关心您的应用程序是否连接到 Redux 存储。
另一方面,容器组件处理行为部分,应该包含非常有限的dom标记和样式。它们将需要渲染的数据作为道具传递给哑组件。
我在另一个教程React 中的 Stateful vs. Stateless Components 中深入讨论了这个主题。
React Manjunath M 中的 React 有状态与无状态功能组件
继续,让我们看看我们将如何组织我们的组件。
表示组件
以下是我们将在本教程中使用的演示组件。
组件/AddContactForm。jsx
import React from 'react'; const AddContactForm = ({onInputChange, onFormSubmit}) => ( <form> <div className="form-group"> <label htmlFor="emailAddress">Email address</label> <input type="email" class="form-control" name="email" onChange={onInputChange} placeholder="name@example.com" /> </div> {/* Some code omitted for brevity */} <div className="form-group"> <label htmlFor="physicalAddress">Address</label> <textarea className="form-control" name="address" onChange={onInputChange} rows="3"></textarea> </div> <button type="submit" onClick={onFormSubmit} class="btn btn-primary"> Submit </button> </form> ) export default AddContactForm;
这是一个用于添加新联系人的 HTML 表单。组件作为道具接收onInputChange
和onFormSubmit
回调。当输入值改变和表单被提交时触发该onInputChange
事件。onFormSubmit
组件/ContactList.jsx
const ContactList = (props) => { return( <ul className="list-group" id="contact-list"> {props.contactList.map( (contact) => <li key={contact.email} className="list-group-item"> <ContactCard contact = {contact}/> </li> )} </ul>) } export default ContactList;
该组件接收一组联系人对象作为道具,因此命名为ContactList。我们使用该Array.map()
方法提取个人联系方式,然后将该数据传递给 <ContactCard />
.
组件/ContactCard.jsx
const ContactCard = ({contact}) => { return( <div> <div className="col-xs-4 col-sm-3"> {contact.photo !== undefined ? <img src={contact.photo} alt={contact.name} className="img-fluid rounded-circle" /> : <img src="img/profile_img.png" alt ={contact.name} className="img-fluid rounded-circle" />} </div> <div className="col-xs-8 col-sm-9"> <span className="name">{contact.name + ' ' + contact.surname}</span><br/> {/* Some code omitted for brevity */} </div> </div> ) } export default ContactCard;
该组件接收一个联系人对象并显示联系人的姓名和图像。 对于实际应用,在云中托管 javascript 图像可能是有意义的。
容器组件
我们还将构建准系统容器组件。
容器/Contacts.jsx
class Contacts extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { // Retrieve contactlist from the store } render() { return ( <div> <AddContact/> <br /> <ContactList contactList= {this.returnContactList()} /> </div> ); } } export default Contacts;
该returnContactList()
函数检索联系人对象数组并将其传递给 ContactList 组件。由于returnContactList()
从存储中检索数据,我们暂时将该逻辑留空。
容器/AddContacts.jsx
class AddContact extends Component { constructor(props) { super(props); /* Function binding goes here. Omitted for brevity */ } showAddContactBox() { /* Logic for toggling ContactForm */ } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; /* Logic for handling Input Change */ } handleSubmit(e) { e.preventDefault(); /* Logic for hiding the form and update the state */ } /* Renders the AddContactForm */ renderForm() { return( <div className="col-sm-8 offset-sm-2"> <AddContactForm onFormSubmit={this.handleSubmit} onInputChange={this.handleInputChange} /> </div> ) } render() { return( <div> { /* A conditional statement goes here that checks whether the form should be displayed or not */} </div> ) } } export default AddContact;
我们创建了三个对应于三个操作的基本处理程序方法。它们都派发动作来更新状态。在 render 方法中,我们省略了显示/隐藏表单的逻辑,因为我们需要获取状态。
现在让我们看看如何将 react 和 redux 绑定在一起
react-redux 库
默认情况下,React 绑定在 Redux 中不可用。您需要先安装一个名为 react-redux 的额外库。
npm install --save react-redux
该库仅导出您需要记住的两个api<Provider />
,一个组件和一个称为connect()
.
提供者组件
像 Redux 这样的库需要让整个 React 组件树都可以访问存储数据,从根组件开始。提供者模式允许库从上到下传递数据。下面的代码演示了 Provider 如何神奇地将状态添加到组件树中的所有组件中。
演示代码
import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
整个应用程序需要能够访问商店。因此,我们将提供程序包装在应用程序组件周围,然后将我们需要的数据添加到树的上下文中。然后组件的后代可以访问数据。
connect()
方法 _
现在我们已经为我们的应用程序提供了商店,我们需要将 React 连接到商店。您可以与 store 通信的唯一方法是调度操作和检索状态。我们以前曾用于store.dispatch()
调度操作和store.getState()
检索状态的最新快照。可以让您完全做到这connect()
一点,但需要借助称为mapDispatchToProps
和的两种方法mapStateToProps
。我在下面的示例中演示了这个概念:
演示代码
import {connect} from 'react-redux' const AddContact = ({newContact, addContact}) => { return ( <div> {newContact.name} <br /> {newContact.email} <br /> {newContact.phone} <br /> Are you sure you want to add this contact? <span onClick={addContact}> Yes </span> </div> ) } const mapStateToProps = state => { return { newContact : state.contacts.newContact } } const mapDispatchToProps = dispatch => { return { addContact : () => dispatch(addContact()) } } export default connect( mapStateToProps, mapDispatchToProps
mapStateToProps
并且mapDispatchToProps
都返回一个对象,这个对象的键成为连接组件的一个道具。例如,state.contacts.newContact
映射到props.newContact
. 动作创建者 addContact()
映射到props.addContact
.
但要使其正常工作,您需要上面代码片段中的最后一行。
export default connect( mapStateToProps, mapDispatchToProps )(AddContact)
我们不是直接导出 AddContact
组件,而是导出一个连接的组件。连接提供addContact
并newContact
作为<AddContact/>
组件的道具。
如何连接 React 和 Redux
接下来,我们将介绍连接 React 和 Redux 所需遵循的步骤。
安装 react-redux 库
如果你还没有安装 react-redux 库。你可以使用 NPM 或 Yarn 来安装它。
npm install react-redux --save
将 Store 提供给您的 App 组件
首先创建商店。然后,通过将 store 对象作为 prop 传递给<Provider />
.
index.js
import React from 'react'; import {render}from 'react-dom'; import { Provider } from 'react-redux' import App from './App'; import configurestore from './store' const store = configureStore(); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
将 React 容器连接到 Redux
connect 函数用于将 React 容器绑定到 Redux。这意味着您可以使用连接功能:
订阅商店并将其状态映射到您的道具
调度动作并将调度回调映射到你的道具
将应用程序连接到 Redux 后,您可以使用它 this.props
来访问当前状态并分派操作。我将在 AddContact
组件上演示该过程。AddContact
需要调度三个动作并从存储中获取两个属性的状态。让我们看一下代码。
首先, connect
导入 AddContact.jsx。
import { connect } from 'react-redux';
其次,创建两个方法: mapStateToProps
和 mapDispatchToProps
。
function mapStateToProps(state) { return { isHidden : state.ui.isAddContactFormHidden, newContact: state.contacts.newContact } } function mapDispatchToProps(dispatch) { return { onFormSubmit: (newContact) => { dispatch(addContact(newContact)); }, onInputChange: (name,value) => { dispatch(handleInputChange(name,value)); }, onToggle: () => { dispatch(toggleContactForm()); } } }
mapStateToProps
接收商店的状态作为参数。它返回一个对象,该对象描述了 store 的状态如何映射到你的 props 中。 mapDispatchToProps
返回一个类似的对象,该对象描述了调度操作如何映射到您的道具。
最后,我们使用 connect
将 AddContact
组件绑定到这两个函数如下:
export default connect(mapStateToProps, mapDispatchToProps) (AddContact)
更新容器组件以使用道具
组件的 props 现在可以从 store 中读取 state 和 dispatch action。和handeInputChange
的逻辑应更新如下:handleSubmit
showAddContactBox
showAddContactBox() { const { onToggle } = this.props; onToggle(); } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; const { onInputChange } = this.props; onInputChange(name,value); } handleSubmit(e) { e.preventDefault(); this.props.onToggle(); this.props.onFormSubmit(); }
我们已经定义了处理程序方法,但仍然缺少一个部分——render
函数内部的条件语句。
render() { return( <div> { this.props.isHidden === false ? this.renderForm(): <button onClick={this.showAddContactBox} className="btn"> Add Contact </button>} </div> ) }
如果isHidden
为 false,则呈现表单。否则,将呈现一个按钮。
显示联系人
我们已经完成了最具挑战性的部分。现在,剩下的就是将这些联系人显示为列表。Contacts
容器是该逻辑的最佳位置 。
import React, { Component } from 'react'; import { connect } from 'react-redux'; /* Component import omitted for brevity */ class Contact extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { return this.props.contactList; } render() { return ( <div> <br /> <AddContact/> <br /> <ContactList contactList= {this.returnContactList()} /> </div> ); } } function mapStateToProps(state) { return { contactList : state.contacts.contactList, } } export default connect(mapStateToProps, null) (Contact);
我们已经完成了与上面连接联系人组件与 Redux 存储相同的过程。该mapStateToProps
函数将商店对象映射到contactList
道具。然后我们使用 connect 将 props 值绑定到 Contact 组件。connect 的第二个参数为 null,因为我们没有任何要调度的操作。这样就完成了我们的应用程序与 Redux 商店状态的集成。
- 组件/AddContactForm。jsx
- 组件/ContactList.jsx
- 组件/ContactCard.jsx
- 容器/Contacts.jsx
- 容器/AddContacts.jsx
- 提供者组件
- 演示代码
- connect()方法 _
- 演示代码
- 安装 react-redux 库
- 将 Store 提供给您的 App 组件
- index.js
- 将 React 容器连接到 Redux
- 更新容器组件以使用道具