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