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
函數