Redux通过在全局级别设置状态来帮助您管理状态。 在上一教程中,我们很好地了解了Redux架构和Redux的不可或缺的组件,例如动作,动作创建者,商店和reducer。
在本系列的第二篇文章中,我们将增强对Redux的理解,并以我们已经知道的知识为基础。 我们将从创建一个实际的Redux应用程序(联系人列表)开始,该应用程序比基本计数器要复杂。 这将帮助您加深对上一教程中介绍的单一存储和多个reducers概念的理解。 然后,稍后我们将讨论将Redux状态与React应用程序绑定以及从头开始创建项目时应考虑的最佳实践。
但是,如果您还没有阅读第一篇文章,那也没关系-只要您了解Redux的基础知识,您仍然应该可以继续学习。 仓库中提供了该教程的代码,您可以以此为起点。
使用Redux创建联系人列表
我们将构建具有以下功能的基本联系人列表:
- 显示所有联系人
- 搜索联系人
- 从服务器获取所有联系人
- 添加新联系人
- 将新的联系人数据推入服务器
这是我们的应用程序的外观:
一口气覆盖所有内容很难。 因此,在本文中,我们将只关注Redux部分,即添加新联系人并显示新添加的联系人。 从Redux的角度来看,我们将初始化状态,创建商店,添加reducer和action等。
在下一个教程中,我们将学习如何连接React和Redux以及如何从React前端调度Redux动作。 在最后一部分,我们将重点转移到使用Redux进行API调用。 这包括从服务器获取联系人,并在添加新联系人时发出服务器请求。 除此之外,我们还将创建一个搜索栏功能,使您可以搜索所有现有联系人。
创建状态树的草图
您可以从我的GitHub存储库下载react-redux演示应用程序 。 克隆仓库,并使用v1分支作为起点。 v1分支与create-react-app模板非常相似。 唯一的区别是,我添加了一些空目录来组织Redux。 这是目录结构。
.
├── package.json
├── public
├── README.md
├── src
│ ├── actions
│ ├── App.js
│ ├── components
│ ├── containers
│ ├── index.js
│ ├── reducers
│ └── store
└── yarn.lock
或者,您可以从头开始创建一个新项目。 无论哪种方式,您都需要先安装基本的样板和redux,然后才能开始。
最好先对状态树进行粗略的概述。 我认为,从长远来看,这将为您节省大量时间。 这是可能的状态树的粗略草图。
const initialState = {
contacts: {
contactList: [],
newContact: {
name: '',
surname: '',
email: '',
address: '',
phone: ''
},
ui: {
//All the UI related state here. eg: hide/show modals,
//toggle checkbox etc.
}
}
}
我们的商店需要具有两个属性contacts
和ui
。 contacts属性负责所有与联系人相关的状态,而ui
处理UI特定的状态。 Redux中没有硬性规则可以阻止您将ui
对象放置为contacts
的子状态。 随意以对您的应用有意义的方式组织状态。
newContact
属性中嵌套了两个属性: contactlist
和newContact
。 所述contactlist
是触头阵列,而newContact
临时存储联系信息而接触形式被填充。 我将以此为起点来构建我们的联系列表应用程序。
如何组织Redux
Redux对如何构建应用程序没有意见。 有一些流行的模式,在本教程中,我将简要介绍其中一些。 但是,您应该选择一种模式并坚持下去,直到完全了解所有部分如何连接在一起为止。
您会发现最常见的模式是Rails样式的文件和文件夹结构。 您将拥有几个顶级目录,如下所示:
- 组件:用于存储哑的React组件的位置。 这些组件并不关心您是否正在使用Redux。
- container:用于将操作分派到Redux存储的智能React组件的目录。 redux和react之间的绑定将在此处进行。
- 动作:动作创建者将进入该目录。
- reducers:每个reducer都有一个单独的文件,您将把所有reducer逻辑放在此目录中。
- store:初始化状态和配置存储的逻辑将在此处进行。
下图演示了如果遵循此模式,应用程序的外观:
Rails样式应适用于中小型应用程序。 但是,当您的应用程序增长时,您可以考虑采用域样式方法或与域样式紧密相关的其他流行替代方法。 在这里,每个功能都有其自己的目录,并且与该功能(域)相关的所有内容都在其中。 下图比较了两种方法,左侧是Rails风格,右侧是domain风格。
现在,继续为组件 , 容器 , 商店 , reducer和action创建目录。 让我们从商店开始。
单店,多减速机
让我们为创建一个原型 首先是商店和减速机 。 在前面的示例中,这是商店的外观:
const store = createStore( reducer, {
contacts: {
contactlist: [],
newContact: { }
},
ui: {
isContactFormHidden: true
}
})
const reducer = (state, action) => {
switch(action.type) {
case "HANDLE_INPUT_CHANGE":
break;
case "ADD_NEW_CONTACT":
break;
case "TOGGLE_CONTACT_FORM":
break;
}
return state;
}
switch语句具有三种情况,分别对应于我们将要创建的三个动作。 这里简要说明了这些操作的含义。
-
HANDLE_INPUT_CHANGE
:当用户在联系表单中输入新值时,将触发此操作。 -
ADD_NEW_CONTACT
:当用户提交表单时,将分派此操作。 -
TOGGLE_CONTACT_FORM
:这是一个UI动作,负责显示/隐藏联系表单。
尽管这种幼稚的方法行之有效,但是随着应用程序的增长,使用这种技术将有一些缺点。
- 我们正在使用一个减速器 。 尽管目前只使用一个reducer还可以,但请想象将所有业务逻辑都放在一个非常大的reducer下。
- 上面的代码不遵循我们在上一节中讨论的Redux结构 。
为了解决单个reducer的问题,Redux有一个称为CombineReducers的方法,该方法使您可以创建多个reducer,然后将它们组合为一个reduce函数。 CombineReducers功能增强了可读性。 所以,我要减速分裂成两个,一个contactsReducer
和uiReducer
。
在上面的示例中, createStore
接受一个可选的第二个参数,它是初始状态。 但是,如果要拆分化简器,可以将整个initialState
移到一个新的文件位置,例如reducers / initialState.js 。 然后,我们将initialState
的子集导入每个reducer文件。
分解减速器
让我们重组代码以解决这两个问题。 首先,创建一个名为store / createStore.js的新文件,并添加以下代码:
import {createStore} from 'redux';
import rootReducer from '../reducers/';
/*Create a function called configureStore */
export default function configureStore() {
return createStore(rootReducer);
}
接下来,在reducers / index.js中创建根减速器,如下所示:
import { combineReducers } from 'redux'
import contactsReducer from './contactsReducer';
import uiReducer from './uiReducer';
const rootReducer =combineReducers({
contacts: contactsReducer,
ui: uiReducer,
})
export default rootReducer;
最后,我们需要为contactsReducer
和uiReducer
创建代码。
reducers / contactsReducer.js
import initialState from './initialState';
export default function contactReducer(state = initialState.contacts, action) {
switch(action.type) {
/* Add contacts to the state array */
case "ADD_CONTACT": {
return {
...state,
contactList: [...state.contactList, state.newContact]
}
}
/* Handle input for the contact form.
The payload (input changes) gets merged with the newContact object
*/
case "HANDLE_INPUT_CHANGE": {
return {
...state, newContact: {
...state.newContact, ...action.payload }
}
}
default: return state;
}
}
reducers / uiReducer.js
import initialState from './initialState';
export default function uiReducer(state = initialState.ui, action) {
switch(action.type) {
/* Show/hide the form */
case "TOGGLE_CONTACT_FORM": {
return {
...state, isContactFormHidden: !state.isContactFormHidden
}
}
default: return state;
}
}
创建减速器时,请始终牢记以下几点:减速器的状态需要具有默认值,并且始终需要返回一些值。 如果减速器不遵循该规格,则会出现错误。
由于我们已经涵盖了很多代码,因此让我们看一下我们对方法所做的更改:
- 已引入
combineReducers
调用,以将拆分减速器连接在一起。 - 所述的状态
ui
对象将被处理uiReducer
并通过触点的状态contactsReducer
。 - 为了使减速器保持纯净,已使用了传播算子。 三点语法是散布运算符的一部分。 如果您对传播语法不满意,则应考虑使用像Immutability.js这样的库。
- 不再将初始值指定为
createStore
的可选参数。 相反,我们为其创建了一个单独的文件,名为initialState.js 。 我们要导入initialState
,然后通过执行state = initialState.ui
设置默认状态。
状态初始化
这是reducers / initialState.js文件的代码。
const initialState = {
contacts: {
contactList: [],
newContact: {
name: '',
surname: '',
email: '',
address: '',
phone: ''
},
},
ui: {
isContactFormHidden: true
}
}
export default initialState;
动作与动作创作者
让我们添加几个动作和动作创建者,以添加处理表单更改,添加新联系人以及切换UI状态。 回想一下,动作创建者只是返回动作的函数。 在actions / index.js中添加以下代码。
export const addContact =() => {
return {
type: "ADD_CONTACT",
}
}
export const handleInputChange = (name, value) => {
return {
type: "HANDLE_INPUT_CHANGE",
payload: { [name]: value}
}
}
export const toggleContactForm = () => {
return {
type: "TOGGLE_CONTACT_FORM",
}
}
每个操作都需要返回一个type属性。 该类型就像是一个键,用于确定调用哪个reducer以及如何响应该操作更新状态。 有效负载是可选的,实际上您可以随意调用它。
在我们的案例中,我们创建了三个动作。
TOGGLE_CONTACT_FORM
不需要有效负载,因为每次触发动作时, ui.isContactFormHidden
的值ui.isContactFormHidden
被切换。 布尔值操作不需要有效负载。
表单值更改时,将触发HANDLE_INPUT_CHANGE
操作。 因此,例如,假设用户正在填写电子邮件字段。 然后,该操作将接收"email"
和"bob@example.com"
作为输入,并且移交给reducer的有效负载是一个看起来像这样的对象:
{
email: "bob@example.com"
}
减速器使用此信息来更新newContact
状态的相关属性。
调度动作和订阅商店
下一步的逻辑步骤是调度操作。 一旦分派了动作,状态就会相应地改变。 为了调度动作并获取更新的状态树,Redux提供了某些存储动作。 他们是:
-
dispatch(action)
:调度可能触发状态更改的操作。 -
getState()
:返回应用程序的当前状态树。 -
subscriber(listener)
:每次分派操作且状态树的某些部分发生更改时都会调用的更改侦听器。
转到index.js文件,并导入configureStore
函数和我们之前创建的三个操作:
import React from 'react';
import {render}from 'react-dom';
import App from './App';
/* Import Redux store and the actions */
import configureStore from './store/configureStore';
import {toggleContactForm,
handleInputChange} from './actions';
接下来,创建一个store
对象,并添加一个侦听器,该侦听器在每次分派动作时记录状态树:
const store = configureStore();
//Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
最后,调度一些动作:
/* returns isContactFormHidden returns false */
store.dispatch(toggleContactForm());
/* returns isContactFormHidden returns false */
store.dispatch(toggleContactForm());
/* updates the state of contacts.newContact object */
store.dispatch(handleInputChange('email', 'manjunath@example.com'))
unsubscribe;
如果一切正常,您应该在开发人员控制台中看到这一点。
而已! 在开发者控制台中,您可以看到正在记录的Redux存储,因此您可以查看每个操作后它的变化。
摘要
我们已经为我们很棒的联系人列表应用程序创建了一个简单的Redux应用程序。 我们了解了减速器,拆分减速器以使我们的应用程序结构更整洁,并编写了使商店发生变化的操作。
在发布结束时,我们使用store.subscribe()
方法订阅了商店。 从技术上讲,如果您要在React中使用React和Redux,这不是最好的方法。 有更多优化的方法来将React前端与Redux连接。 我们将在下一个教程中介绍这些内容。
翻译自: https://code.tutsplus.com/tutorials/getting-started-with-redux-learn-by-example--cms-30350