摘要:發(fā)現(xiàn)最近看到的框架入門(mén)程序都變成,我花了三個(gè)小時(shí)才自己實(shí)現(xiàn)了一個(gè)感嘆前端入門(mén)越來(lái)越復(fù)雜了,懷念幾年前還是的時(shí)代。。。主要負(fù)責(zé)的渲染,和那個(gè)框的功能。利用對(duì)的值進(jìn)行控制,然后監(jiān)聽(tīng)鍵盤(pán)來(lái)判斷是輸入還是提交。需要講解的不多。。。
發(fā)現(xiàn)最近看到的框架入門(mén)程序都變成Todos,我花了三個(gè)小時(shí)才自己實(shí)現(xiàn)了一個(gè)Todos...感嘆前端入門(mén)越來(lái)越復(fù)雜了,懷念幾年前還是hello world的時(shí)代。。。
吐槽不多說(shuō),這里主要說(shuō)的只是React和Redux這一塊兒,css樣式完全是從這里抄過(guò)來(lái)的。
代碼的結(jié)構(gòu)準(zhǔn)備是這個(gè)樣子的:
轉(zhuǎn)化成代碼結(jié)構(gòu),就是這個(gè)樣子:
另外,按照官方示例,在Header左邊的toggleAll按鈕放到了Section中。
Redux Types/todos.js在Redux中,type是用于action和reducer交流的時(shí)候的一個(gè)flag,讓reducer知道這是一個(gè)什么請(qǐng)求。
我比較習(xí)慣把type多帶帶分離出來(lái),列到一個(gè)文件里面,讓Redux的文件更干凈,并便于管理。
/** ------------------------- TODO ---------------------*/ export const TODO_INSERT_ITEM = "TODO_INSERT_ITEM"; export const TODO_DELETE_ITEM = "TODO_DELETE_ITEM"; export const TODO_SWITCH_FILTER = "TODO_SWITCH_FILTER"; export const TODO_TOGGLE_ACTIVE = "TODO_TOGGLE_ACTIVE"; export const TODO_TOGGLE_ALL = "TODO_TOGGLE_ALL"; export const TODO_CHANGE_VALUE = "TODO_CHANGE_VALUE"; export const TODO_CLEAR_COMPLETED = "TODO_CLEAR_COMPLETED";Actions/todos.js
根據(jù)上面列出的type,列出對(duì)應(yīng)的action creator
import { TODO_INSERT_ITEM, TODO_DELETE_ITEM, TODO_SWITCH_FILTER, TODO_TOGGLE_ACTIVE, TODO_TOGGLE_ALL, TODO_CHANGE_VALUE, TODO_CLEAR_COMPLETED } from "../types"; // 插入一個(gè)TODO export function insertItem(value){ return { type: TODO_INSERT_ITEM, value }; } // 刪除一個(gè)TODO export function deleteItem(id) { return { type: TODO_DELETE_ITEM, id } } // 轉(zhuǎn)換一個(gè)TODO的狀態(tài) export function switchFilter(filter) { return { type: TODO_SWITCH_FILTER, filter } } // 清楚所有完成的TODO export function clearCompleted(){ return { type: TODO_CLEAR_COMPLETED } } export function toggleActive(id){ return { type: TODO_TOGGLE_ACTIVE, id } } // 轉(zhuǎn)換所有的狀態(tài)到active export function toggleAll(active){ return { type: TODO_TOGGLE_ALL, active } } // 改變對(duì)應(yīng)TODO的值 export function changeValue(id, value) { return { type: TODO_CHANGE_VALUE, id, value } }Reducers/todos.js
在reducer中需要注意幾點(diǎn):
初始化的state要從localStorage中獲取
每次做出修改,都要重新更新localStorage
數(shù)據(jù)沒(méi)有發(fā)生改變的時(shí)候,盡量使用原數(shù)據(jù),減少re-render
為了便于查找,我在這里用了lodash的uniqueId方法,給每一個(gè)item加一個(gè)id
為了便于儲(chǔ)存和展示,我這里包含一個(gè)items用來(lái)保存所有的items,一個(gè)showedItems用來(lái)儲(chǔ)存需要展示的items
先提供一個(gè)簡(jiǎn)單的簡(jiǎn)寫(xiě)localStorage方法
const local = (function(KEY){ return { set: value=>{ localStorage.setItem(KEY, value) }, get: ()=>localStorage.getItem(KEY), check: ()=>localStorage.getItem(KEY) != undefined }; })("todo");
然后幾個(gè)輔助的方法:
// 制造一個(gè)新的item function generateItem(value) { return { id: _.uniqueId(), active: true, value } } // 判斷當(dāng)前的item是否正在展示 function include(active, filter) { return filter === "ALL" || (active && filter === "ACTIVE") || (!active && filter === "COMPLETED"); } // 獲取頁(yè)面上需要展示的items function getShowedItems(items, filter) { let showedItems = [], keys = Object.keys(items); for(let i = 0; i < keys.length; i++){ let item = items[keys[i]]; if(include(item.active, filter)) { showedItems.push(item); } } return showedItems; }
初始化的時(shí)候,獲取localStorage中的值,或者給一個(gè)默認(rèn)值:
let defaultTodo; (function(){ if(local.check()) { defaultTodo = JSON.parse(local.get()); } else { defaultTodo = { items: {}, filter: "ALL", // ALL, COMPLETED, ACTIVE count: 0, showedItems: [], hasCompleted: false } } })();
注:在這里提一句,由于我不喜歡文檔中把所有的處理方法放在一個(gè)函數(shù)里面的方式,所以我寫(xiě)了一個(gè)方法,把reducers分開(kāi)成多個(gè)函數(shù)
// 很簡(jiǎn)單,其實(shí)就是循環(huán)調(diào)用。。。 export function combine(reducers){ return (state, action) => { for(let key in reducers) { if(reducers.hasOwnProperty(key)) { state = reducers[key](state, action) || state; } } return state; } }
下面上所有的reducers,具體邏輯就不多說(shuō)了:
let exports = {}; exports.insertItem = function(state = defaultTodo, action) { const type = action.type; if(type === TODO_INSERT_ITEM) { let { count, items, filter, showedItems } = state; let item = generateItem(action.value); items = { ...items, [item.id] : item } count = count + 1; state = { ...state, items, count, showedItems: filter !== "COMPLETED" ? getShowedItems(items, filter) : showedItems } local.set(JSON.stringify(state)); } return state; } exports.deleteItem = function(state = defaultTodo, action) { const type = action.type; if(type === TODO_DELETE_ITEM && state.items[action.id]) { let { count, items, filter, hasCompleted } = state; let item = items[action.id]; delete items[action.id]; if(item.active) count--; state = { ...state, items, count, showedItems: include(item.active, filter) ? getShowedItems(items, filter) : state.showedItems, hasCompleted: Object.keys(items).length !== count } local.set(JSON.stringify(state)); } return state; } exports.switchFilter = function(state = defaultTodo, action) { const type = action.type; if(type === TODO_SWITCH_FILTER && state.filter !== action.filter) { state = { ...state, filter: action.filter, showedItems: getShowedItems(state.items, action.filter) } local.set(JSON.stringify(state)); } return state; } exports.clearCompleted = function(state = defaultTodo, action) { const type = action.type; if(type === TODO_CLEAR_COMPLETED) { let { items, filter, showedItems } = state; let keys = Object.keys(items); let tempItems = {}; for(let i = 0; i < keys.length; i++) { let item = items[keys[i]]; if(item.active) { tempItems[item.id] = item; } } state = { ...state, items: tempItems, showedItems: filter === "ACTIVE" ? showedItems : getShowedItems(tempItems, filter), hasCompleted: false } local.set(JSON.stringify(state)); } return state; } exports.toggleActive = function(state = defaultTodo, action) { const { type, id } = action; if(type === TODO_TOGGLE_ACTIVE && state.items[id]) { let { items, filter, count, showedItems } = state; let item = items[id]; item.active = !item.active; items = { ...items, [id]: item }; if(item.active) count++; // 如果變?yōu)閍ctive else count--; // 如果變?yōu)閏ompleted state = { ...state, items, count, showedItems: getShowedItems(items, filter), hasCompleted: Object.keys(items).length !== count } local.set(JSON.stringify(state)); } return state; } exports.toggleAll = function(state = defaultTodo, action) { const { type, active } = action; if(type === TODO_TOGGLE_ALL) { let { items, filter, showedItems } = state; let keys = Object.keys(items); for(let i = 0; i < keys.length; i++) { items[keys[i]].active = active; } let count = active ? keys.length : 0; state = { ...state, items, count, showedItems: include(active, filter) ? getShowedItems(items, filter) : showedItems, hasCompleted: !active } local.set(JSON.stringify(state)); } return state; } exports.changeValue = function(state = defaultTodo, action){ const { type, id } = action; if(type === TODO_CHANGE_VALUE && state.items[id]) { let { items, filter, showedItems } = state; let item = items[id]; item.value = action.value; items = { ...items, [id]: item }; state = { ...state, items, showedItems: include(item.active, filter) ? getShowedItems(items, filter) : showedItems } local.set(JSON.stringify(state)); } return state; } export default combine(exports); // 用combine方法包裹
Reducers中,我在很多的showedItems都做了是否發(fā)生改變的檢查,如果沒(méi)有發(fā)生改變,那么就用原來(lái)的,便于在Section組件中,可以避免不必要的重新渲染。雖然,在我這里似乎沒(méi)有什么用。不過(guò)對(duì)復(fù)雜的項(xiàng)目還是有必要的。
Views/Todos.jsimport React from "react"; import Header from "containers/ToDo/Header"; import Footer from "containers/ToDo/Footer"; import Section from "containers/ToDo/Section"; import "components/ToDo/index.scss"; export default class ToDo extends React.Component { constructor(props) { super(props); } render(){ return (Contianers Header.js) } }
Header.js主要負(fù)責(zé)logo的渲染,和那個(gè)input框的功能。
利用controlled component對(duì)input的值進(jìn)行控制,然后監(jiān)聽(tīng)鍵盤(pán)來(lái)判斷是輸入還是提交。
import React from "react"; import { CONTROLS } from "utils/KEYCODE"; import { connect } from "react-redux"; import { insertItem } from "actions/todo"; class Header extends React.Component { constructor(props) { super(props); this.state = { value: "" }; } onChange = (e)=>{ let value = e.target.value; this.setState({ value }); } onKeyDown = (e)=>{ let keyCode = e.keyCode; if(keyCode === CONTROLS.ENTER && this.state.value !== "") { this.props.insertItem(this.state.value); this.setState({ value: "" }); e.preventDefault(); e.stopPropagation(); } } render(){ return (Footer.js) } } export default connect(null, { insertItem })(Header); todos
Footer主要是用于展示數(shù)量,filter, Clear Completed按鈕
import React, { PropTypes } from "react"; import { connect } from "react-redux"; import { switchFilter, clearCompleted } from "actions/todo"; class Footer extends React.Component { constructor(props) { super(props); } switchFilter = (filter)=>{ this.props.switchFilter(filter.toUpperCase()); } render(){ const { count, hasCompleted, filter, clearCompleted } = this.props; if(count === 0 && !hasCompleted) return null; return ( ) } } Footer.propTypes = { count: PropTypes.number.isRequired, hasCompleted: PropTypes.bool.isRequired, filter: PropTypes.oneOf(["ALL", "ACTIVE", "COMPLETED"]).isRequired } function mapStateToProps(state){ let { todo: { count, hasCompleted, filter } } = state; return { count, hasCompleted, filter } } export default connect(mapStateToProps, { switchFilter, clearCompleted })(Footer);Section.js
Section包含Todos的列表,還有刪除, 改變狀態(tài),修改value, toggle all等功能。
import React, { PropTypes } from "react"; import { connect } from "react-redux"; import { deleteItem, toggleActive, toggleAll, changeValue } from "actions/todo"; import Item from "components/ToDo/Item"; class Section extends React.Component { constructor(props) { super(props); } render(){ const { showedItems=[], count, toggleAll, changeValue, deleteItem, toggleActive, hasCompleted } = this.props; return (Components Item.js{ toggleAll(count === 0) }} checked={count === 0 && hasCompleted} /> ) } } Section.propTypes = { showedItems: PropTypes.arrayOf(PropTypes.object).isRequired, count: PropTypes.number.isRequired } function mapStateToProps(state) { let { todo: { showedItems, count, hasCompleted } } = state; return { showedItems, count, hasCompleted }; } export default connect(mapStateToProps, { deleteItem, toggleActive, toggleAll, changeValue })(Section);{ showedItems.map(item=>
- ) }
import React from "react"; import ReactDOM from "react-dom"; export default class Item extends React.Component { constructor(props) { super(props); this.state = { value: props.value, editing: false }; } componentDidUpdate() { if(this.state.editing) { var node = ReactDOM.findDOMNode(this.edit); node.focus(); } } inputInstance = (input) => { this.edit = input; } onToggle = (e)=>{ this.props.toggleActive(this.props.id, !e.target.checked); } onValueChange = (e)=>{ this.props.onValueChange(this.props.id, e.target.value); this.setState({ editing: false }); } onEditChange = (e)=>{ this.setState({ value: e.target.value }); } onDoubleClick = (e)=>{ this.setState({ editing: true }); } render(){ let { id, active, onItemDelete, onValueChange } = this.props; let { value, editing } = this.state; return (
寫(xiě)組件的時(shí)候,感覺(jué)代碼貼出來(lái)看看就好了。需要講解的不多。。。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/81325.html
摘要:入門(mén)實(shí)例前端技術(shù)真是日新月異,搞完不搭配個(gè)數(shù)據(jù)流都不好意思了。關(guān)于的用法,這只是基礎(chǔ)入門(mén)的部分,還有的多的搞基操作,比如異步數(shù)據(jù)流和配合。 redux —— 入門(mén)實(shí)例 TodoListshowImg(https://segmentfault.com/img/bVtSeH); Tip 前端技術(shù)真是日新月異,搞完 React 不搭配個(gè)數(shù)據(jù)流都不好意思了。滿懷期待的心去翻了翻 flux,簡(jiǎn)直...
摘要:系列文章入門(mén)本文進(jìn)階番外篇狀態(tài)管理,第一次聽(tīng)到這個(gè)詞要追溯到去年年底。只讀的唯一改變的方法就是觸發(fā),是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。沒(méi)有特殊情況沒(méi)有副作用,沒(méi)有請(qǐng)求沒(méi)有變量修改,只進(jìn)行單純執(zhí)行計(jì)算。 系列文章: Redux 入門(mén)(本文) Redux 進(jìn)階 番外篇: Vuex — The core of Vue application 狀態(tài)管理,第一次聽(tīng)到這個(gè)詞要追溯到去年年...
摘要:用于簡(jiǎn)單可擴(kuò)展的狀態(tài)管理,相比有更高的靈活性,文檔參考中文文檔,本文作為入門(mén),介紹一個(gè)簡(jiǎn)單的項(xiàng)目。任務(wù)已完成下一個(gè)任務(wù)修復(fù)谷歌瀏覽器頁(yè)面顯示問(wèn)題提交意見(jiàn)反饋代碼創(chuàng)建在中引入主入口文件設(shè)置參考入門(mén)學(xué)習(xí)總結(jié) MobX用于簡(jiǎn)單、可擴(kuò)展的React狀態(tài)管理,相比Redux有更高的靈活性,文檔參考:MobX中文文檔,本文作為入門(mén),介紹一個(gè)簡(jiǎn)單的TodoList項(xiàng)目。 1. 預(yù)期效果 showIm...
摘要:開(kāi)發(fā)前需要安裝和以及一些需要用到的中間件如果在要使用的話,還需要引入這個(gè)庫(kù)或者使用示例下面通過(guò)實(shí)現(xiàn)一個(gè)快速上手。然后開(kāi)始創(chuàng)建處理這兩個(gè)指令的。完成上述三步之后,我們就可以在應(yīng)用的主頁(yè)使用相應(yīng)修改并取得新的數(shù)據(jù)了。 本文適合有一定React和Redux基礎(chǔ)的用戶閱讀。 前言的前言 最近被一款來(lái)自京東凹凸實(shí)驗(yàn)室的多終端開(kāi)發(fā)框架Taro吸粉了,官方對(duì) Taro 的簡(jiǎn)介是使用React語(yǔ)法,一...
摘要:而函數(shù)式編程就不一樣了,這是模仿我們?nèi)祟?lèi)的思維方式發(fā)明出來(lái)的。數(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)用的過(guò)程。 代碼地址:Re...
閱讀 2141·2021-11-18 10:07
閱讀 3517·2021-09-04 16:48
閱讀 3221·2019-08-30 15:53
閱讀 1245·2019-08-30 12:55
閱讀 2460·2019-08-29 15:08
閱讀 3161·2019-08-29 15:04
閱讀 2885·2019-08-29 14:21
閱讀 2915·2019-08-29 11:21