摘要:發現最近看到的框架入門程序都變成,我花了三個小時才自己實現了一個感嘆前端入門越來越復雜了,懷念幾年前還是的時代。。。主要負責的渲染,和那個框的功能。利用對的值進行控制,然后監聽鍵盤來判斷是輸入還是提交。需要講解的不多。。。
發現最近看到的框架入門程序都變成Todos,我花了三個小時才自己實現了一個Todos...感嘆前端入門越來越復雜了,懷念幾年前還是hello world的時代。。。
吐槽不多說,這里主要說的只是React和Redux這一塊兒,css樣式完全是從這里抄過來的。
代碼的結構準備是這個樣子的:
轉化成代碼結構,就是這個樣子:
另外,按照官方示例,在Header左邊的toggleAll按鈕放到了Section中。
Redux Types/todos.js在Redux中,type是用于action和reducer交流的時候的一個flag,讓reducer知道這是一個什么請求。
我比較習慣把type多帶帶分離出來,列到一個文件里面,讓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
根據上面列出的type,列出對應的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"; // 插入一個TODO export function insertItem(value){ return { type: TODO_INSERT_ITEM, value }; } // 刪除一個TODO export function deleteItem(id) { return { type: TODO_DELETE_ITEM, id } } // 轉換一個TODO的狀態 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 } } // 轉換所有的狀態到active export function toggleAll(active){ return { type: TODO_TOGGLE_ALL, active } } // 改變對應TODO的值 export function changeValue(id, value) { return { type: TODO_CHANGE_VALUE, id, value } }Reducers/todos.js
在reducer中需要注意幾點:
初始化的state要從localStorage中獲取
每次做出修改,都要重新更新localStorage
數據沒有發生改變的時候,盡量使用原數據,減少re-render
為了便于查找,我在這里用了lodash的uniqueId方法,給每一個item加一個id
為了便于儲存和展示,我這里包含一個items用來保存所有的items,一個showedItems用來儲存需要展示的items
先提供一個簡單的簡寫localStorage方法
const local = (function(KEY){ return { set: value=>{ localStorage.setItem(KEY, value) }, get: ()=>localStorage.getItem(KEY), check: ()=>localStorage.getItem(KEY) != undefined }; })("todo");
然后幾個輔助的方法:
// 制造一個新的item function generateItem(value) { return { id: _.uniqueId(), active: true, value } } // 判斷當前的item是否正在展示 function include(active, filter) { return filter === "ALL" || (active && filter === "ACTIVE") || (!active && filter === "COMPLETED"); } // 獲取頁面上需要展示的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; }
初始化的時候,獲取localStorage中的值,或者給一個默認值:
let defaultTodo; (function(){ if(local.check()) { defaultTodo = JSON.parse(local.get()); } else { defaultTodo = { items: {}, filter: "ALL", // ALL, COMPLETED, ACTIVE count: 0, showedItems: [], hasCompleted: false } } })();
注:在這里提一句,由于我不喜歡文檔中把所有的處理方法放在一個函數里面的方式,所以我寫了一個方法,把reducers分開成多個函數
// 很簡單,其實就是循環調用。。。 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,具體邏輯就不多說了:
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++; // 如果變為active else count--; // 如果變為completed 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都做了是否發生改變的檢查,如果沒有發生改變,那么就用原來的,便于在Section組件中,可以避免不必要的重新渲染。雖然,在我這里似乎沒有什么用。不過對復雜的項目還是有必要的。
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主要負責logo的渲染,和那個input框的功能。
利用controlled component對input的值進行控制,然后監聽鍵盤來判斷是輸入還是提交。
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主要是用于展示數量,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的列表,還有刪除, 改變狀態,修改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 (
寫組件的時候,感覺代碼貼出來看看就好了。需要講解的不多。。。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81325.html
摘要:入門實例前端技術真是日新月異,搞完不搭配個數據流都不好意思了。關于的用法,這只是基礎入門的部分,還有的多的搞基操作,比如異步數據流和配合。 redux —— 入門實例 TodoListshowImg(https://segmentfault.com/img/bVtSeH); Tip 前端技術真是日新月異,搞完 React 不搭配個數據流都不好意思了。滿懷期待的心去翻了翻 flux,簡直...
摘要:用于簡單可擴展的狀態管理,相比有更高的靈活性,文檔參考中文文檔,本文作為入門,介紹一個簡單的項目。任務已完成下一個任務修復谷歌瀏覽器頁面顯示問題提交意見反饋代碼創建在中引入主入口文件設置參考入門學習總結 MobX用于簡單、可擴展的React狀態管理,相比Redux有更高的靈活性,文檔參考:MobX中文文檔,本文作為入門,介紹一個簡單的TodoList項目。 1. 預期效果 showIm...
摘要:開發前需要安裝和以及一些需要用到的中間件如果在要使用的話,還需要引入這個庫或者使用示例下面通過實現一個快速上手。然后開始創建處理這兩個指令的。完成上述三步之后,我們就可以在應用的主頁使用相應修改并取得新的數據了。 本文適合有一定React和Redux基礎的用戶閱讀。 前言的前言 最近被一款來自京東凹凸實驗室的多終端開發框架Taro吸粉了,官方對 Taro 的簡介是使用React語法,一...
摘要:而函數式編程就不一樣了,這是模仿我們人類的思維方式發明出來的。數據流在中,數據的流動是單向的,即從父節點傳遞到子節點。數據流嚴格的單向數據流是架構的設計核心。 前言 總括: 本文采用react+redux+react-router+less+es6+webpack,以實現一個簡易備忘錄(todolist)為例盡可能全面的講述使用react全家桶實現一個完整應用的過程。 代碼地址:Re...
閱讀 2130·2021-11-18 10:07
閱讀 3507·2021-09-04 16:48
閱讀 3214·2019-08-30 15:53
閱讀 1235·2019-08-30 12:55
閱讀 2453·2019-08-29 15:08
閱讀 3149·2019-08-29 15:04
閱讀 2879·2019-08-29 14:21
閱讀 2907·2019-08-29 11:21