摘要:入門實(shí)例前端技術(shù)真是日新月異,搞完不搭配個數(shù)據(jù)流都不好意思了。關(guān)于的用法,這只是基礎(chǔ)入門的部分,還有的多的搞基操作,比如異步數(shù)據(jù)流和配合。
redux —— 入門實(shí)例 TodoList
前端技術(shù)真是日新月異,搞完 React 不搭配個數(shù)據(jù)流都不好意思了。
滿懷期待的心去翻了翻 flux,簡直被官方那意識流的文檔折服了,真是又臭又長還是我智商問題??
轉(zhuǎn)戰(zhàn) redux ,越看越有意思,跟著文檔做了個 TodoList 的入門小例子。
廢話不多說,先貼上文章用到例子的源碼 https://github.com/TongchengQiu/TodoList-as-redux-demo
redux 的 Github 倉庫 https://github.com/rackt/redux
還有個中文的 gitbook 翻譯文檔 http://camsong.github.io/redux-in-chinese/index.html
隨著spa(不是SPA,是單頁應(yīng)用)的發(fā)展,以 react 來說,組件化和狀態(tài)機(jī)的思想真是解放了煩惱的 dom 操作,一切都為狀態(tài)。state 來操縱 views 的變化。
然而,因為頁面的組件化,導(dǎo)致每個組件都必須維護(hù)自身的一套狀態(tài),對于小型應(yīng)用還好。
但是對于比較大的應(yīng)用來說,過多的狀態(tài)顯得錯綜復(fù)雜,到最后難以維護(hù),很難清晰地組織所有的狀態(tài),在多人開發(fā)中也是如此,導(dǎo)致經(jīng)常會出現(xiàn)一些不明所以的變化,越到后面調(diào)試上也是越麻煩,很多時候 state 的變化已經(jīng)不受控制。對于組件間通行、服務(wù)端渲染、路由跳轉(zhuǎn)、更新調(diào)試,我們很需要一套機(jī)制來清晰的組織整個應(yīng)用的狀態(tài),redux 應(yīng)然而生,這種數(shù)據(jù)流的思想真是了不起。
在 react 中,我們盡量會把狀態(tài)放在頂層的組件,在頂層組件使用 redux 或者 router。
這就把組件分為了兩種:容器組件和展示組件。
容器組件:和 redux 和 router 交互,維護(hù)一套狀態(tài)和觸發(fā) action。
展示組件:展示組件是在容器組件的內(nèi)部,他們不維護(hù)狀態(tài),所有數(shù)據(jù)通過 props 傳給他們,所有操作也是通過回調(diào)完成。
這樣,我們整套應(yīng)用的架構(gòu)就顯得清晰了。
redux 分為三大部分,store , action ,reducer 。
store整個應(yīng)用的 state 被儲存在一棵 object tree 中,并且這個 object tree 只存在于唯一一個 store 中。
或者這么說 store 的指責(zé)有這些:
維護(hù)整個應(yīng)用的 state
提供 getState() 方法獲取 state;
提供 dispatch(action) 方法更新 state;
通過 subscribe(listener) 注冊監(jiān)聽器。
這么解釋一下,整個應(yīng)用的 state 都儲存在 store 中,容器組件可以從 store 中獲取所需要的狀態(tài)。
容器組件同時也可以發(fā)送給 store 一個 action,告訴他改變某個狀態(tài)的值,所以說容器組件只要發(fā)送一個指令,就可以叫 store 去 setState,然后 store 的 state 改變,回過來容器組件獲取到的 state 改變,導(dǎo)致 views 的更新。
action 可以理解為一種指令,store 數(shù)據(jù)的唯一由來就是 action,action 是一個對象,它需要至少一個元素,type,type 是這個指令的唯一標(biāo)識,其它元素是傳送這個指令的 state 值
{ type: ACTION_TYPE, text: “content”, }
這個指令由組件觸發(fā),然后傳到 reducer。
reduceraction 只是說明了要去做什么,和做這件事情需要的參數(shù)值。
具體去改變 store 中的 state 是由 reducer 來做的。
reducer 其實(shí)是一個包含 switch 的函數(shù),前面不是說組件觸發(fā)的 action 會傳遞到 reducer,reducer 接收這個參數(shù) action,他通過 switch(action.type) 然后做不同操作,前面說了,這個 type 是指令的標(biāo)識,reducer 根據(jù)這個標(biāo)識來作出不同的操作。
這個操作是什么呢?
reducer 還接收另一個參數(shù) state,這個是舊的 state。從 action 里面還可以獲取到做這個操作需要的 參數(shù)值。
這個操作其實(shí)就是對原有的 state 和 從 action 中的到的值,來進(jìn)行操作(結(jié)合,刪除,...)然后返回一個 新的 state 到 store。
把前面的語言組織一下,整個操作的數(shù)據(jù)流其實(shí)是這樣的:
store 把整個應(yīng)用的 state,getState(),dispatch(),subscribe() 傳給頂層容器組件;
容器組件和三個部分交互:
內(nèi)部的展示組件:容器把狀態(tài)分發(fā)給各個組件,把 dispatch(操作數(shù)據(jù)的函數(shù))以回調(diào)的形式分發(fā)給各個組件;
action:容器獲取 action;
reducer:容器可以調(diào)用 dispatch(action),這個上面說了,會以回調(diào)的形式給下面的子組件,這樣就可以根據(jù)不同的用戶操作,調(diào)用不同的 dispatch(action),執(zhí)行了這個函數(shù)之后,就把 action 傳給 reducer,然后看 reducer;
reducer 得到容器組件傳來的 action 之后,根據(jù) action.type 這個參數(shù)執(zhí)行不同操作,他還會接收到 store 里面的原 state,然后把原 state 和 action 對象里面的其它參數(shù)進(jìn)行操作,然后 return 一個新的對象。
reducer return 一個新的對象到 store,store 根據(jù)這個新對象,更新應(yīng)用狀態(tài)。
----一個循環(huán) ??
connectRedux 和 React 之間沒有關(guān)系,他們并補(bǔ)互相依賴,但是 Redux 和 React 搭配起來簡直完美。
我們可以通過 react-redux 這個庫把他們綁定起來
npm install --save react-redux
react-redux 提供兩個東西 Provider 和 connect。
Provider這個 Provider 其實(shí)就是一個中間件,他是在原有 App Container 上面再包一層,他的作用就是接收 store 里面的 store 作為 props,將store放在context里,給下面的connect用的。
connect這個組件才是真正連接 Redux 和 React,他包在我們的容器組件的外一層,他接收上面 Provider 提供的 store 里面的 state 和 dispatch,傳給一個構(gòu)造函數(shù),返回一個對象,以屬性形式床給我們的容器組件。
實(shí)戰(zhàn) TodoList這個項目使用 webpack 來構(gòu)建,想要了解 webpack 的配置可以看我的其它兩篇文章:
如何使用webpack—webpack-howto;
webpack-best-practice-最佳實(shí)踐-部署生產(chǎn).
. ├── app #開發(fā)目錄 | | | ├──actions #action的文件 | | | ├──components #內(nèi)部組件 | | | ├──containers #容器組件 | | | ├──reducers #reducer文件 | | | ├──stores #store配置文件 | | | └──index.js #入口文件 | ├── dist #發(fā)布目錄 ├── node_modules #包文件夾 ├── .gitignore ├── .jshintrc ├── server.js #本地靜態(tài)服務(wù)器 ├── webpack.config.js #webpack配置文件 └── package.json
這里,我們只關(guān)注我們的 app 開發(fā)目錄。
index.js 入口文件import React from "react"; import { render } from "react-dom"; import { Provider } from "react-redux"; import App from "./containers/App"; import configureStore from "./stores/configureStore"; const store = configureStore(); render(, document.getElementById("root") );
這里我們從 react-redux 中獲取了一個 Provider 組件,我們把它渲染到應(yīng)用的最外層。
他需要一個屬性 store ,他把這個 store 放在context里,給App(connect)用。
app/stores.configureStore.js
import { createStore } from "redux"; import rootReducer from "../reducers"; export default function configureStore(initialState) { const store = createStore(rootReducer, initialState); if (module.hot) { module.hot.accept("../reducers", () => { const nextReducer = require("../reducers"); store.replaceReducer(nextReducer); }); } return store; }
他從 redux 拿到 createStore 這個函數(shù),再獲取到 rootReducer ;
createStore 函數(shù)接收兩個參數(shù),(reducer, [initialState]),reducer 毋庸置疑,他需要從 store 獲取 state,以及連接到 reducer 交互。
initialState 是可以自定義的一個初始化 state,可選參數(shù)。
module.hot這個可以不用管,這是 webpack 熱加載的處理,你也可以不要他。
containers/App.jsx
import React, { Component, PropTypes } from "react"; import { connect } from "react-redux"; import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from "../actions"; import AddTodo from "../components/AddTodo"; import TodoList from "../components/TodoList"; import Footer from "../components/Footer"; class App extends Component { render() { const { dispatch, visibleTodos, visibilityFilter } = this.props; return (); } } App.propTypes = { visibleTodos: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired, completed: PropTypes.bool.isRequired })), visibilityFilter: PropTypes.oneOf([ "SHOW_ALL", "SHOW_COMPLETED", "SHOW_ACTIVE" ]).isRequired }; function selectTodos(todos, filter) { switch (filter) { case VisibilityFilters.SHOW_ALL: return todos; case VisibilityFilters.SHOW_COMPLETED: return todos.filter(todo => todo.completed); case VisibilityFilters.SHOW_ACTIVE: return todos.filter(todo => !todo.completed); } } // 這里的 state 是 Connect 的組件的 function select(state) { return { visibleTodos: selectTodos(state.todos, state.visibilityFilter), visibilityFilter: state.visibilityFilter }; } export default connect(select)(App);dispatch(addTodo(text)) } /> dispatch(completeTodo(index))} />
他從 react-redux 獲取 connect 連接組件,通過 connect(select)(App) 連接 store 和 App 容器組件。
select 是一個函數(shù),他能接收到一個 state 參數(shù),這個就是 store 里面的 state,然后通過這個函數(shù)的處理,返回一個對象,把對象里面的參數(shù)以屬性傳送給 App,以及附帶一個 dispatch。
所以在 App 里面可以:
const { dispatch, visibleTodos, visibilityFilter } = this.props;
所以 App 通過 connect 的到 state 和 dispatch,把 state 傳遞給子組件。
dispatch 這個函數(shù)可以接收一個 action 參數(shù),然后就會執(zhí)行 reducer 里面的操作。
比如:
text => dispatch(addTodo(text))
addTodo(text),這個函數(shù)是在 action 里面的到的,可以看 action 的代碼,他其實(shí)返回一個 action 對象,所以其實(shí)就是dispatch(action) 。
actionapp/actions/index.js
export const ADD_TODO = "ADD_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; export const SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER"; export const VisibilityFilters = { SHOW_ALL: "SHOW_ALL", SHOW_COMPLETED: "SHOW_COMPLETED", SHOW_ACTIVE: "SHOW_ACTIVE", }; export function addTodo(text) { return { type: ADD_TODO, text }; } export function completeTodo(index) { return { type: COMPLETE_TODO, index }; } export function setVisibilityFilter(filter) { return { type: SET_VISIBILITY_FILTER, filter }; }
在聲明每一個返回 action 函數(shù)的時候,我們需要在頭部聲明這個 action 的 type,以便好組織管理。
每個函數(shù)都會返回一個 action 對象,所以在 容器組件里面 調(diào)用
text => dispatch(addTodo(text))
就是調(diào)用dispatch(action) 。
reducerapp/reducers/visibilityFilter.js
import { SET_VISIBILITY_FILTER, VisibilityFilters } from "../actions"; const { SHOW_ALL } = VisibilityFilters; function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } export default visibilityFilter;
這里我們從 actions 獲得各個 type 的參數(shù),以便和 action 做好映射對應(yīng)。
整個函數(shù)其實(shí)就是執(zhí)行 switch,根據(jù)不同的 action.type,返回不同的對象狀態(tài)。
但是如果我們需要 type 很多,比如除了 visibilityFilter,還有 todos,難道要寫一個長長的switch,當(dāng)然不。
redux 提供一個 combineReducers 輔助函數(shù),把一個由多個不同 reducer 函數(shù)作為 value 的 object,合并成一個最終的 reducer 函數(shù),然后就可以對這個 reducer 調(diào)用 createStore。
我們把不同的 reducer 放在不同文件下。
app/reducers/todo.js
import { ADD_TODO, COMPLETE_TODO } from "../actions"; function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ]; case COMPLETE_TODO: return [ ...state.slice(0, action.index), Object.assign({}, state[action.index], { completed: true }), ...state.slice(action.index + 1) ]; default: return state } } export default todos;
然后通過一個 index.js 把他們合并。
app/reducers/index.js
import { combineReducers } from "redux"; import todos from "./todos"; import visibilityFilter from "./visibilityFilter"; const rootReducer = combineReducers({ todos, visibilityFilter }); export default rootReducer;展示組件
app/components/AddTodo/index.jsx
import React, { Component, PropTypes } from "react"; import { findDOMNode } from "react-dom"; export default class AddTodo extends Component { render() { return (); } handleClick(e) { const inputNode = findDOMNode(this.refs.input); const text = inputNode.value.trim(); this.props.onAddClick(text); inputNode.value = ""; } } AddTodo.propTypes = { onAddClick: PropTypes.func.isRequired };
app/components/Todo/index.jsx
import React, { Component, PropTypes } from "react"; export default class Todo extends Component { render() { const { onClick, completed, text } = this.props; return (
app/components/TodoList/index.jsx
import React, { Component, PropTypes } from "react"; import Todo from "../Todo"; export default class TodoList extends Component { render() { return (
app/components/Footer/index.jsx
import React, { Component, PropTypes } from "react"; export default class Footer extends Component { renderFilter(filter, name) { if(filter == this.props.filter) { return name; } return ( { e.preventDefault(); this.props.onFilterChange(filter); }}> {name} ); } render() { return (SHOW {" "} {this.renderFilter("SHOW_ALL", "All")} {", "} {this.renderFilter("SHOW_COMPLETED", "Completed")} {", "} {this.renderFilter("SHOW_ACTIVE", "Active")} .
); } } Footer.propTypes = { onFilterChange: PropTypes.func.isRequired, filter: PropTypes.oneOf([ "SHOW_ALL", "SHOW_COMPLETED", "SHOW_ACTIVE" ]).isRequired };
可以看出,所有的展示組件需要的 state 和 數(shù)據(jù),都從屬性中獲取的,所有的操作,都是通過容器組件給的回調(diào)函數(shù)來操作的。
他們盡可能地不擁有自己的狀態(tài),做無狀態(tài)組件。
關(guān)于 redux 的用法,這只是基礎(chǔ)入門的部分,還有的多的搞基操作,比如異步數(shù)據(jù)流、Middleware、和 router 配合。
敬請期待~~~~
???????????????????
我的博客原文redux 大法好 —— 入門實(shí)例 TodoList;
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/78341.html
摘要:而函數(shù)式編程就不一樣了,這是模仿我們?nèi)祟惖乃季S方式發(fā)明出來的。數(shù)據(jù)流在中,數(shù)據(jù)的流動是單向的,即從父節(jié)點(diǎn)傳遞到子節(jié)點(diǎn)。數(shù)據(jù)流嚴(yán)格的單向數(shù)據(jù)流是架構(gòu)的設(shè)計核心。 前言 總括: 本文采用react+redux+react-router+less+es6+webpack,以實(shí)現(xiàn)一個簡易備忘錄(todolist)為例盡可能全面的講述使用react全家桶實(shí)現(xiàn)一個完整應(yīng)用的過程。 代碼地址:Re...
摘要:首先聲明這篇文章是想說明一下最新版本的的新特性帶來的極大的開發(fā)體驗提升而不是如何利用開發(fā)應(yīng)用這個特性就是對的支持在的中有說明具體可以參考這里在版本之前我們在開發(fā)應(yīng)用尤其是在配合一類庫的時候經(jīng)常用到諸如之類的封裝而這些函數(shù)其實(shí)都可以用裝飾器的 首先聲明, 這篇文章是想說明一下最新版本的 TypeScript(3.0) 的新特性帶來的極大的 React 開發(fā)體驗提升. 而不是如何利用 Ty...
摘要:用于簡單可擴(kuò)展的狀態(tài)管理,相比有更高的靈活性,文檔參考中文文檔,本文作為入門,介紹一個簡單的項目。任務(wù)已完成下一個任務(wù)修復(fù)谷歌瀏覽器頁面顯示問題提交意見反饋代碼創(chuàng)建在中引入主入口文件設(shè)置參考入門學(xué)習(xí)總結(jié) MobX用于簡單、可擴(kuò)展的React狀態(tài)管理,相比Redux有更高的靈活性,文檔參考:MobX中文文檔,本文作為入門,介紹一個簡單的TodoList項目。 1. 預(yù)期效果 showIm...
摘要:描述了如何把轉(zhuǎn)變成下一個。唯一的要點(diǎn)是當(dāng)變化時需要返回全新的對象,而不是修改傳入的參數(shù)。以上是純的使用,使用起來比較雞肋,大量被使用在項目中,封裝庫提供的和可以將和完美結(jié)合,使用非常方便。 @subject: wepy-redux-time-todo @author: leinov @date:2018-10-30 wepy-redux-time-todo showImg(ht...
閱讀 3469·2019-08-30 13:15
閱讀 1405·2019-08-29 18:34
閱讀 833·2019-08-29 15:18
閱讀 3490·2019-08-29 11:21
閱讀 3253·2019-08-29 10:55
閱讀 3707·2019-08-26 10:36
閱讀 1876·2019-08-23 18:37
閱讀 1832·2019-08-23 16:57