Redux 减速器是如何工作的
已发表: 2022-03-10state
时使用过 Redux,那么您肯定会遇到 reducer。 本教程将解释 reducer 的概念以及它们在 Redux 中的具体工作方式。在本教程中,我们将学习 reducer 的概念以及它们是如何工作的,特别是在 React 应用程序中。 为了理解和更好地使用 Redux,对 reducer 有扎实的了解是必不可少的。 Reducers 提供了一种使用操作更新应用程序状态的方法。 它是 Redux 库的一个组成部分。
本教程适用于想要了解更多有关 Redux Reducers 的开发人员。 了解 React 和 Redux 将是有益的。 在本教程的最后,您应该对 Reducer 在 Redux 中所扮演的角色有了更好的理解。 我们将编写代码演示和一个应用程序,以更好地理解 Reducers 以及它如何影响应用程序中的状态。
什么是减速机
Reducer 是一个纯函数,它将应用程序和操作的状态作为参数并返回一个新状态。 例如,身份验证reducer 可以采用空对象形式的应用程序的初始状态和告诉它用户已登录并使用已登录用户返回新应用程序状态的操作。
纯函数是没有任何副作用的函数,如果传入相同的参数,将返回相同的结果。
下面是一个纯函数的例子:
const add = (x, y) => x + y; add(2, 5);
上面的示例根据输入返回一个值,如果您传递2
和5
,那么您总是会得到7
,只要它是相同的输入,就不会影响您获得的输出,这是纯函数的一个示例。
下面是一个接收状态和动作的 reducer 函数的示例。
const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }
让我们定义 reducer 接收的两个参数state
和action
。
状态
状态是您的组件正在使用的数据——它保存组件所需的数据,并指示组件呈现的内容。 一旦state
对象发生变化,组件就会重新渲染。 如果应用程序状态由 Redux 管理,那么 reducer 就是状态更改发生的地方。
行动
一个动作,是一个包含信息负载的对象。 它们是要更新的 Redux 存储的唯一信息来源。 Reducers 根据action.type
的值更新存储。 在这里,我们将action.type
定义为ADD_TO_CART
。
根据官方 Redux 文档,动作是触发 Redux 应用程序更改的唯一事物,它们包含更改应用程序商店的有效负载。 Actions 是 JavaScript 对象,它告诉 Redux 要执行的操作类型,通常它们被定义为如下函数:
const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }
上面的代码是一个典型的payload
值,其中包含用户正在发送的内容,它将用于更新应用程序的状态。 正如您从上面看到的,操作对象包含执行此特定操作所必需的操作类型和有效负载对象。
使用 Reducer 更新状态
为了展示 reducer 的工作原理,让我们看看下面的数字计数器:
const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };
在上面的代码中, increaseAction
和decreaseAction
是 reducer 中用于确定state
更新为的操作。 接下来,我们有一个名为countReducer
的 reducer 函数,它接受一个action
和一个值为0
的初始state
。 如果action.type
的值为INCREASE
,我们返回一个增加 1 的新状态,否则如果它是DECREASE
,则返回一个减少 1 的新状态。 如果没有这些条件,我们返回state
。
使用 Reducer 更新状态:Spread 运算符
状态不能直接更改,要创建或更新状态,我们可以使用 JavaScript 扩展运算符来确保我们不会直接更改状态的值,而是返回一个包含传递给它的状态的新对象,并且用户的有效载荷。
const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }
在上面的代码中,我们使用扩展运算符来确保我们不直接更改状态值,这样我们可以返回一个新对象,该对象填充了传递给它的状态和由用户。 通过使用扩展运算符,我们可以确保状态保持不变,因为我们向其中添加了所有新项目,并且如果之前存在,还可以替换状态中的联系人字段。
Redux Reducers 实战 — 演示
为了更好地理解 Redux Reducers 及其工作原理,我们将实现一个简单的电影细节查找器应用程序,代码和工作版本可以在 Codesandbox 上找到。 要开始,请转到您的终端并使用以下命令初始化一个反应应用程序:
create-react-app movie-detail-finder
一旦我们的项目初始化,接下来让我们安装我们的应用程序需要的包。
npm i axios reactstrap react-redux redux redux-thunk
一旦安装了软件包,让我们使用以下命令启动我们的开发服务器:
npm start
上面的命令应该在我们的浏览器中启动我们的项目开发服务器。 接下来让我们在我们选择的文本编辑器中打开我们的项目,在我们的项目src
文件夹中,删除以下文件: App.css
、 App.test.js
、 serviceWorker.js
和setupTests.js
。 接下来,让我们删除所有引用App.js
上已删除文件的代码。
对于这个项目,我们将使用 Open Movie Database API 为我们的应用程序获取我们的电影信息、内容和图像,这是 API 的链接,您需要注册并获取访问密钥才能使用它应用程序,完成后,让我们通过构建组件来继续我们的应用程序。
构建应用程序组件
首先,在我们的项目目录中的src
文件夹中,创建一个名为 components 的文件夹,在该文件夹中,让我们创建两个名为Movie
和Searchbar
的文件夹,我们的组件应该如下图所示:
构建电影组件
让我们构建Movies
组件,它将概述我们将从 API 获取的电影细节的结构。 为此,在我们组件的Movies
文件夹中,创建一个新文件Movie.js
,接下来为 API 结果创建一个基于类的组件,让我们在下面执行此操作。
import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;
在上面的代码中,使用包reactstrap
中的组件,您可以在此处查看文档。 我们构建了一个卡片组件,其中包括电影名称、图像、流派、演员、年份、电影作者、评级和情节。 为了更容易地从这个组件传递数据,我们构建了数据作为其他组件的道具。 接下来,让我们构建我们的Searchbar
组件。
构建我们的搜索栏组件
我们的Searchbar
组件将具有一个搜索栏和一个用于搜索电影组件的按钮组件,让我们在下面这样做:
import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }
在上面的代码中,我们从react-redux
导入connect
,它用于将 React 组件连接到 Redux 存储区,为组件提供存储区的信息,并提供用于向存储区调度操作的函数。 接下来,我们从动作中导入了Movie
组件和函数fetchMovie
。
接下来,我们有一个带有用于输入电影标题的输入框的表单标签,使用 React 中的setState
钩子,我们添加了一个onChange
事件和值,它将title
的状态设置为输入框中输入的值。 我们有一个button
标签来搜索电影标题,并使用我们导入的Movie
组件,我们将组件的属性作为props
传递给搜索结果。
接下来为我们编写一个函数来将我们的电影标题提交给 API 以便将结果发送给我们,我们还需要设置应用程序的初始状态。 让我们在下面这样做。
class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }
在这里,我们将应用程序的初始状态设置为空字符串,我们创建了一个函数formHandler
,它接受一个事件参数并从 action 传递fetchMovie
函数,并将标题设置为应用程序的新状态。 为了完成我们的应用程序,让我们使用react-redux
的 connect 属性导出这个组件,为此我们将使用 react redux 的mapToStateProps
属性来选择我们的组件需要的数据部分,您可以在此处了解有关mapToStateProps
的更多信息。
const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)
让我们通过创建文件Searchbar.module.css
并添加以下样式来为表单添加样式:
.Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }
完成上述操作后,我们的搜索栏组件应类似于下图:
为应用程序创建操作
在这个组件中,我们将为我们的应用程序设置 Redux 操作,首先,在src
目录中,创建一个名为actions
的文件夹,在该文件夹中,我们将创建一个index.js
文件。 在这里,我们将创建一个函数fetchMovie
,它接受一个标题参数,并使用 Axios 从 API 中获取电影。 让我们在下面这样做:
import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }
在上面的代码中,我们导入了axios
并创建了一个名为fetchMovie
的函数,它通过使用 async/await 接收一个title
参数,以便我们可以向 API 服务器发出请求。 我们有一个dispatch
函数,它将传递给它的 action 对象分派给 Redux。 根据我们上面的内容,我们正在调度一个类型为FETCH_MOVIE
的操作和包含我们从 API 获得的响应的有效负载。
注意:在apikey
注册后,请求中的 apikey 将替换为您自己apikey
apikey 。
创建 App Reducer
在本节中,我们将为我们的应用程序创建 reducer。
const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;
在上面的代码中,我们创建了一个fetchMovieReducer
,它采用默认状态null
和一个action
参数,使用 switch 运算符,对于FETCH_MOVIE
,我们将返回action.payload
的值,这是我们从 API 获得的电影。 如果我们尝试执行的操作不在 reducer 中,那么我们返回默认状态。
接下来,我们创建了一个rootReducer
函数,它将接受当前状态和一个动作作为输入并返回fetchMovieReducer
。
把它放在一起
在本节中,我们将通过在index.js
中创建我们的 redux 存储来完成我们的应用程序,让我们在下面这样做:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )
在上面的代码中,我们使用createStore
方法通过传递我们创建的 reducer 和一个中间件来创建应用store
。 中间件是允许我们增强 Redux 功能的插件。 在这里,我们使用applyMiddleware
来使用 Redux Thunk 中间件。 Redux Thunk 中间件是我们的商店进行异步更新所必需的。 这是必需的,因为默认情况下,Redux 会同步更新存储。
为了确保我们的应用程序知道要使用的确切 store,我们将应用程序包装在一个Provider
组件中,并将 store 作为 prop 传递,通过这样做,我们应用程序中的其他组件可以连接并与 store 共享信息。
让我们为index.css
文件添加一些样式。
*{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }
渲染和测试电影细节查找器
在本节中,我们将通过在App.js
中渲染我们的应用程序来结束我们的应用程序,为此,让我们创建一个名为App
的基于类的组件并初始化我们的搜索栏和输入字段。
import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;
在这里,我们创建了一个基于 App 类的组件,其h1
表示 Movie Search App,并添加了我们的Searchbar
组件。 我们的应用程序应如下图所示:
Codesandbox 上提供了一个工作演示。
结论
Reducers 是 Redux 状态管理的重要组成部分,使用 reducer,我们可以编写纯函数来更新 Redux 应用程序的特定区域,而不会产生副作用。 我们已经了解了 Redux reducer 的基础知识、它们的用途,以及 reducer、状态和参数的核心概念。
您可以通过在此处查看有关 Redux reducers 的文档来进一步了解这一点。 您可以更进一步并在 Redux reducer 上构建更多内容,让我知道您构建了什么。
资源
- React-Redux 文档
- Redux 文档
connect()
函数applyMiddleware
函数