首先,我们需要使用create-react-app命令行工具安装新的react应用。
运行以下命令安装react app
。
npx create-react-app redux-tutorial
上面的命令将把与React相关的文件下载到“ redux-tutorial”文件夹中。
一旦成功安装,请使用以下命令将工作目录更改为应用程序目录。
代码语言:javascript复制cd redux-tutorial
npm start
npm start
命令用于打开本地开发服务器localhost:3000
。
安装Redux库
让我们使用以下命令安装redux
andreact-redux
库。
npm i redux react-redux
redux:Redux用于管理状态
react-redux:用于在react和redux库之间进行绑定。
现在,使用您喜欢的代码编辑器打开“ redux-tutorial”文件夹。
Reducer
Reducer函数是一个纯函数,它采用上一个应用程序状态,type of action
并返回下一个状态而不会改变前一个状态。
Redux遵循不变性,这意味着我们不改变应用程序状态,而不是返回 新的应用程序状态。
Redux在单个JavaScript对象中管理整个应用程序状态。
reducers
在src
目录中创建一个新文件夹。
在reducers内,文件夹创建一个名为的新文件reducer.js。
// reducer.js
const intialState = { name: "reactgo", allNames: []}
const reducer = (state = intialState, action) => {
if (action.type === "ADDNAME") {
return {
allNames: state.allNames.concat(state.name),
name: ""
}
}
if (action.type === "CHANGE_NAME") {
return {
...state,
name: action.name
}
}
return state
}
export default reducer;
在上面的代码中,我们定义了带有两个参数state
和的reducer函数action
。在reducer函数内部,我们添加了两个条件语句。我们的初始状态对象是 { name: "", allNames: []}
。
将React与Redux连接
代码语言:javascript复制// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import App from './App';
import reducer from './reducers/reducer'
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
在index.js
文件内部,我们从“ redux”库中导入了createStore函数,并从react-redux
库中导入 Provider 组件。
我们通过将函数作为参数传递来调用createStore函数,并通过传递store属性reducer
将<Provider>
组件与<App/>
组件包装 在一起。
<Provider>
组件使用react context API通过组件树向下传递状态。
从组件访问Redux状态
现在我们可以直接从React组件访问我们的redux状态。
打开App.js
文件并添加以下代码。
mapStatetoProps示例
代码语言:javascript复制import React, { Component } from 'react';
import { connect } from 'react-redux'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<div>
<input type="text"
placeholder="Name"
value={this.props.name} />
</div>
);
}
}
const mapStatetoProps = (state) => {
return {
name: state.name,
}
}
export default connect(mapStatetoProps)(App);
在这里,我们首先导入connect
从react-redux
库中调用的高阶组件,然后使用state
参数定义一个函数mapStatetoProps。
通过使用状态参数, 我们可以访问在reducer
函数内部定义的redux状态。
我们在mapStatetoProps
函数内部定义的任何属性都可以用作App
组件内部的props ,例如,在上面的组件中,我们返回的对象带有{name:state.name},
这样我们就可以以这样的形式访问组件name
内部的该属性。Appthis.props.name
最后,我们需要connect,
通过将mapStatetoProps
参数作为参数来调用该函数。
如果现在打开浏览器,您会看到“ reactgo”显示在该input
字段内。
改变Redux状态
redux状态树是只读的,我们不能直接改变状态。
在redux中,我们只能通过调用dispatch
类型为的方法来改变状态action
。
如果你打开reducer.js
文件,你可以看到他们有两种类型,其可用的action为ADDNAME
和CHANGE_NAME
。
reducer.js
const intialState = { name: "reactgo", allNames: []}
const reducer = (state = intialState, action) => {
if (action.type === "ADDNAME") {
return {
allNames: state.allNames.concat(state.name),
name: ""
}
}
if (action.type === "CHANGE_NAME") {
return {
...state,
name: action.name
}
}
return state
}
export default reducer;
如果我们使用类型调用方法ADDNAME,
那么我们将返回新状态,并将 name
属性值添加到allNames
数组中并重置name
属性。
让我们看看实际情况。
打开App.js
文件,添加以下代码。
mapDispatchtoProps示例
代码语言:javascript复制// App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import './App.css'
class App extends Component {
handleNameChange = (e) => {
this.props.onChangeName(e.target.value)
}
render() {
return (
<div className="App">
<div>
<input type="text"
placeholder="Name"
value={this.props.name} onChange={this.handleNameChange} />
<button onClick={this.props.onAddName}>Add name</button>
<ul>
{this.props.allNames && this.props.allNames.map(name => (
<li key={name}> {name}</li>
))}
</ul></div>
</div>
);
}
}
const mapStatetoProps = (state) => {
return {
name: state.name,
allNames: state.allNames
}
}
const mapDispatchtoProps = (dispatch) => {
return {
onChangeName: (name) => dispatch({ type: "CHANGE_NAME", name: name }),
onAddName: () => dispatch({ type: "ADDNAME" }),
}
}
export default connect(mapStatetoProps, mapDispatchtoProps)(App);
在上面,我们定义了一个mapDispatchtoProps
函数,将dispatch
方法作为函数参数。
在mapDispatchtoProps
函数内部,我们返回了一个具有两个属性的对象onChangeName
, onAddName
onChangeName
:它可以帮助我们了解用户添加dispatch
的操作类型CHANGE_NAME
和有效负载name
属性。
onAddName
:它有助于我们dispatch
采取行动类型ADDNAME
。
我们可以App
作为来访问组件内部的这两个属性props
。
现在让我们在浏览器中对其进行测试。
错误处理
我们还可以通过ERROR
在reducer函数中创建一个类型来处理错误。
reducer.js
使用以下代码更新文件:
// reducer.js
const intialState = { name: "reactgo", allNames: [], error: "" }
const reducer = (state = intialState, action) => {
if (action.type === "ADDNAME") {
return {
allNames: state.allNames.concat(state.name),
name: ""
}
}
if (action.type === "CHANGE_NAME") {
return {
...state,
name: action.name
}
}
if (action.type === "ERROR") {
return {
...state,
error: action.error
}
}
return state
}
export default reducer;
在上面的代码中,我们reducer
通过添加第三个条件语句来更新我们的函数,该条件语句的类型ERROR
和error
属性被添加到我们的initialState对象中。
让我们ERROR
在App
组件中dispatch action类型。
// App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import './App.css'
class App extends Component {
handleNameChange = (e) => {
this.props.onChangeName(e.target.value)
}
handleClick = () => {
if (this.props.name) {
this.props.onAddName()
} else {
this.props.onError("Name field cannot be empty")
}
}
render() {
return (
<div className="App">
<div>
<input type="text"
placeholder="Name"
value={this.props.name} onChange={this.handleNameChange} />
<button onClick={this.handleClick}>Add name</button>
<p className={this.props.error ? "error active" : "error"}>
{this.props.error}</p>
<ul>
{this.props.allNames && this.props.allNames.map(name => (
<li key={name}> {name}</li>
))}
</ul></div>
</div>
);
}
}
const mapStatetoProps = (state) => {
return {
name: state.name,
error: state.error,
allNames: state.allNames
}
}
const mapDispatchtoProps = (dispatch) => {
return {
onChangeName: (name) => dispatch({ type: "CHANGE_NAME", name: name }),
onAddName: () => dispatch({ type: "ADDNAME" }),
onError: (err) => dispatch({ type: "ERROR", error: err })
}
}
export default connect(mapStatetoProps, mapDispatchtoProps)(App);
在上面的代码中,我们在handleClick
方法内部添加了条件检查,以便每当用户尝试单击Add name
按钮而不输入名称时,我们都会 通过传递错误消息来调用this.props.onError方法。
重构代码
很难在许多地方手动键入操作类型,因此我们要创建两个新文件,分别是actionCreators.js
和actionTypes.js
在actionTypes.js
文件中,我们正在定义所有动作类型。
目前,我们的应用程序中包含三种类型的操作CHANGE_NAME
,ADDNAME
以及ERROR
actions
在src
目录中创建一个文件夹。
在actions
文件夹内创建一个actionTypes.js
文件和以下代码。
// actionTypes.js
export const ADDNAME = "ADDNAME";
export const CHANGE_NAME = "CHANGE_NAME";
export const ERROR = "ERROR";
actionCreators.js
在actions
文件夹中创建一个新文件。
// actionCreators.js
import { CHANGE_NAME, ADDNAME, ERROR } from './actionTypes'
export function changeName(name) {
return {
type: CHANGE_NAME,
name: name
}
}
export function addname() {
return {
type: ADDNAME
}
}
export function error(msg) {
return {
type: ERROR,
error: msg
}
}
在上面,我们创建了三个action创建器,它们返回三种不同类型的动作。
Action创建者是JavaScript函数,它们以一种action类型返回对象。
使用更新您的reducer.js
文件actionTypes
// reducer.js
import { CHANGE_NAME, ADDNAME, ERROR } from '../actions/actionTypes'
const intialState = { name: "reactgo", allNames: [], error: "" }
const reducer = (state = intialState, action) => {
if (action.type === ADDNAME) {
return {
allNames: state.allNames.concat(state.name),
name: ""
}
}
if (action.type === CHANGE_NAME) {
return {
...state,
name: action.name
}
}
if (action.type === ERROR) {
return {
...state,
error: action.error
}
}
return state
}
export default reducer;
App.js
使用actionCreate()更新文件。
// App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { addname, error, changeName } from './actions/actionCreators'
import './App.css'
class App extends Component {
handleNameChange = (e) => {
this.props.onChangeName(e.target.value)
}
handleClick = () => {
if (this.props.name) {
this.props.onAddName()
} else {
this.props.onError("Name field cannot be empty")
}
}
render() {
return (
<div className="App">
<div>
<input type="text"
placeholder="Name"
value={this.props.name} onChange={this.handleNameChange} />
<button onClick={this.handleClick}>Add name</button>
<p className={this.props.error ? "error active" : "error"}>
{this.props.error}</p>
<ul>
{this.props.allNames && this.props.allNames.map(name => (
<li key={name}> {name}</li>
))}
</ul></div>
</div>
);
}
}
const mapStatetoProps = (state) => {
return {
name: state.name,
error: state.error,
allNames: state.allNames
}
}
const mapDispatchtoProps = (dispatch) => {
return {
onChangeName: (name) => dispatch(changeName(name)),
onAddName: () => dispatch(addname()),
onError: (err) => dispatch(error(err))
}
}
export default connect(mapStatetoProps, mapDispatchtoProps)(App);