最近一直在學習react技術棧,相關的理論和概念基本都瞭解了,以前也用reactjs寫了幾個demo,切身體會到了函數式編程和組件化開發的強大之處,但因各類主客觀緣由,過後沒有對相關知識點進行梳理和總結,並且工做中也沒用到,致使如今複習的時候生疏了,還須要花大部分時間從新理清需求和邏輯,作了不少重複性的工做,太得不償失了。爲了提升本身的學習效率,避免作一些無用的工做,我也決定之後(不管是工做仍是學習)必定要養成定時總結的習慣,並且也要用文字記錄下來,這樣能夠時常複習,理清邏輯,加深印象。另外,關於我我的的學習總結,若有不對的地方,歡迎批評指正,期待共同提升!不喜勿噴,謝謝!
├── components | └──app.css//樣式文件 ├── node_modules //依賴包 ├── static //靜態文件 | └──index.html //入口html文件 | └──bundle.js //編譯後的js文件 ├── index.js //主入口js文件 ├── package.json //項目所依賴的npm包 ├── webpack.config.js //webpack配置文件 └── yarn.lock //依賴或者更新包相關版本信息。這樣能夠解決同一個項目在不一樣機器上環境不一致的問題。
package.json
{ "name": "todolist", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "start": "webpack-dev-server --line --hot", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "www.icrazyman.cn", "license": "ISC", "devDependencies": { "babel-core": "^6.21.0", "babel-loader": "^6.2.10", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "css-loader": "^0.28.4", "react": "^15.4.1", "react-dom": "^15.4.1", "style-loader": "^0.18.2", "webpack": "^1.14.0", "webpack-dev-server": "^1.16.2" }, "dependencies": { "node": "^6.11.1" } }
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Redux Todos Example</title> </head> <body> <div class="todoapp" id="root"></div> <script src="bundle.js"></script> </body> </html>
index.js
import第三方依賴包和css文件
import React from 'react' import { render } from 'react-dom' import './components/app.css' render( <div className='todo-box'> <div className='todo-innerBox'> <div className='todo-tab'> <div className='todo-tab_item'> <a href='javascript:;'>所有任務</a> </div> <div className='todo-tab_item'> <a href='javascript:;'>待辦任務</a> </div> <div className='todo-tab_item'> <a href='javascript:;'>已完成任務</a> </div> </div> <ul className='list-group'> <li className="todo-list_li"> <input type="checkbox" className="pull-left" value="on" /> aaa </li> <li className="todo-list_li"> <input type="checkbox" className="pull-left" value="on" /> bbb </li> <li className="todo-list_li"> <input type="checkbox" className="pull-left" value="on" /> ccc </li> </ul> <div> <form> <input placeholder='你想作點什麼' className='todo-input' /> <button type='submit' className='todo-btn'>添加任務</button> </form> </div> </div> </div>, document.getElementById('root') )
webpack.config.js
module.exports = { devtool: 'eval-source-map', //選擇map來加強調試過程 entry: __dirname + '/index.js', //入口文件 output: { path: __dirname + '/static',//打包生成路徑 filename: 'bundle.js' }, module: { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015', 'react'] } }, { test: /\.css$/, loader: 'style-loader!css-loader' }] }, devServer: { //熱更新 contentBase: './static', historyApiFallback: true, inline: true, hot: true } }
yarn
或npm install
安裝依賴,運行npm run build
編譯,運行npm start
進行查看頁面是否正常顯示小結:這個階段只是把頁面實現出來了,尚未實現任何邏輯。其中頁面實現的步驟爲:
1. 在index.html編寫html結構和css樣式 2. 把html結構提取到index.js組件中同時轉換成jsx語法 3. 把css樣式提取到app.css中
├── actions | └──index.js//管理狀態 ├── components | └──app.css//樣式文件 | └──App.js//UI組件入口文件 | └──Link.js//UI組件 | └──Todo.js//UI組件 | └──Top.js//UI組件 ├── containers | └──VisibleTodoList.js//容器組件 | └──AddTodo.js//容器組件 | └──FilterLink.js//容器組件 ├── reducers | └──index.js//數據邏輯處理文件 ├── node_modules //依賴包 ├── static //靜態文件 | └──index.html //入口html文件 | └──bundle.js //編譯後的js文件 ├── index.js //主入口js文件 ├── package.json //項目所依賴的npm包 ├── webpack.config.js //webpack配置文件 └── yarn.lock //依賴或者更新包相關版本信息。這樣能夠解決同一個項目在不一樣機器上環境不一致的問題。
這次新增react-redux和redux依賴包
{ "name": "todolist", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "start": "webpack-dev-server --line --hot", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "babel-core": "^6.21.0", "babel-loader": "^6.2.10", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "css-loader": "^0.28.4", "react": "^15.4.1", "react-dom": "^15.4.1", "react-redux": "^5.0.1", "redux": "^3.6.0", "style-loader": "^0.18.2", "webpack": "^1.14.0", "webpack-dev-server": "^1.16.2" }, "dependencies": { "node": "^6.11.1" } }
這次利用redux管理整個項目的狀態,而且把項目代碼抽離成組件
//注意:import後不加{}表明引入的default,加了{}表明引入其中導出的一部分,用到了ES6的解構 //注意:import後不加{}表明引入的default,加了{}表明引入其中導出的一部分,用到了ES6的解構 import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' //數據邏輯處理保存在reducers裏 import todoApp from './reducers' import App from './components/App' //在頂層建立store管理整個項目的數據 const store = createStore(todoApp) console.log('9. root index start') //connect方法生成容器組件之後,須要讓容器組件拿到state對象,才能生成 UI 組件的參數。 // 一種解決方法是將state對象做爲參數,傳入容器組件。可是,這樣作比較麻煩,尤爲是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。 // React-Redux 提供Provider組件,可讓容器組件拿到state。 //Provider在根組件外面包了一層,這樣一來,App的全部子組件就默認均可以拿到state了。 render( <Provider store={store}> <App /> </Provider> ,document.getElementById('root') ) console.log('9. root index end')
把數據邏輯處理的代碼抽離成reducers
import { combineReducers } from 'redux' console.log('data flow:') console.log('1. reducers start') /* 傳入舊的state和做用的action返回一個新state */ const todo = (state, action) => { console.log('data flow 4') switch(action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false //新增默認爲未完成 } case 'TOGGLE_TODO': if (state.id !== action.id) { return state } return Object.assign({}, state, {completed: !state.completed}) default: return state } } const todos = (state = [], action) => { console.log('1.1 todos twice') switch(action.type) { case 'ADD_TODO': return [ ...state, todo(undefined, action) ] //todo(undefined, action) 新增一條記錄時第一個參數state不存在 case 'TOGGLE_TODO': return state.map(t => todo(t, action)); default: return state; } } const visibilityFilter = (state = 'SHOW_ALL', action) => { console.log('1.2 visibilityFilter twice') switch (action.type) { case 'SET_VISIBILITY': return action.filter; default: return state; } } console.log('1. reducers end') export default combineReducers({ todos, visibilityFilter });
在redux中,actions是觸發store中數據更新的惟一來源,因此要寫個actions利用dispatch方法來觸發store中管理的狀態更新
let nextTodoId = 0; console.log('2. actions start') // 添加 const addTodo = (text) => { // console.log('text:' + text) console.log('data flow 2') let id = nextTodoId ++ ; return { type: 'ADD_TODO', id: id, text } } // 頂部顯示狀態 const setVisibility = (filter) => { // console.log('filter:' + filter) return { type: 'SET_VISIBILITY', filter } } // 觸發 const toggleTodo = (id) => { // console.log('id:' + id) return { type: 'TOGGLE_TODO', id } } export {addTodo, setVisibility, toggleTodo} console.log('2. actions end')
子組件的入口文件,負責對抽離出的子組件進行組合而後導出
import React from 'react' import Top from './Top' import VisibleTodoList from '../containers/VisibleTodoList' import AddTodo from '../containers/AddTodo' import './app.css' console.log('8. App.js start') const App = () => ( <div className='todo-box'> <div className='todo-innerBox'> <Top /> <VisibleTodoList /> <AddTodo /> </div> </div> ) console.log('8. App.js end') export default App;
TodoList頂部組件
import React from 'react' import { connect } from 'react-redux' import { setVisibility } from '../actions' import FilterLink from '../containers/FilterLink' console.log('4. Top.js start') const Top = () => ( <div className="todo-tab"> <FilterLink filter="SHOW_ALL"> 所有任務 </FilterLink> <FilterLink filter="SHOW_ACTIVE"> 待辦任務 </FilterLink> <FilterLink filter="SHOW_COMPLETED"> 已完成任務 </FilterLink> </div> ); console.log('4. Top.js end') export default Top;
過濾組件
import React from 'react' import { connect } from "react-redux" import { setVisibility } from "../actions" import Link from "../components/Link" //第二個參數表示組件自身的props //mapStateToProps()將state節點注入到與view相關的組件 const mapStateToProps = (state, ownProps) => { // console.log({state,ownProps}) return { active: ownProps.filter === state.visibilityFilter } } //mapDispatchToProps()將須要綁定的響應事件注入到組件上 const mapDispatchToProps = (dispatch, ownProps) => { return { onClick: () => { dispatch(setVisibility(ownProps.filter)) } } } //connent()函數生成容器組件 const FilterLink = connect( mapStateToProps, mapDispatchToProps )(Link) export default FilterLink;
展現組件,顯示頂部狀態按鈕
import React from 'react'; console.log('3. Link.js start') const Link = ({ active, children, onClick }) => { return ( <div className="todo-tab_item"> <a href = "#" style={{ color: active? '#f01414' : '#4d555d' }} onClick = { e => { e.preventDefault() onClick() } }> {children} </a> </div> ) } console.log('3. Link.js end') export default Link;
每個Todo子組件
import React from 'react' console.log('5. Todo.js start') //UI 組件有如下幾個特徵。 // 只負責 UI 的呈現,不帶有任何業務邏輯 // 沒有狀態(即不使用this.state這個變量) // 全部數據都由參數(this.props)提供 // 不使用任何 Redux 的 API // UI 組件又稱爲"純組件",不含狀態,純粹由參數決定它的值 const Todo = ({ onClick, completed, text }) => ( <li className='todo-list_li' style={{textDecoration:completed ? "line-through" : "none"}}> <input type='checkbox' className='pull-left' value='on' onClick={onClick} defaultChecked={completed} /> {text} </li> ) console.log('5. Todo.js end') export default Todo;
TodoList列表組件
import React from 'react' import Todo from '../components/Todo' import { connect } from 'react-redux' import { toggleTodo } from '../actions' console.log('6. VisibleTodoList start') //負責管理數據和業務邏輯,不負責 UI 的呈現 // 帶有內部狀態 // 使用 Redux 的 API const getVisibleTodos = (todos, filter) => { console.log('9.2 getVisibleTodos') switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter(t => t.completed === true)//已完成 case 'SHOW_ACTIVE': return todos.filter(t => t.completed === false)//未完成 default: throw new Error('Unknown filter: ' + filter) } } //合併redux的狀態到react的props中 //將state映射到 UI 組件的參數(props) //mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,從新計算 UI 組件的參數,從而觸發 UI 組件的從新渲染。 //mapStateToProps的第一個參數老是state對象,還可使用第二個參數,表明容器組件的props對象。 //使用ownProps做爲參數後,若是容器組件的參數發生變化,也會引起 UI 組件從新渲染。 const mapStateToProps = (state, ownProps) => { console.log('9.1 mapStateToProps') // console.log(ownProps) // console.log(state) return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } //將用戶對 UI 組件的操做映射成 Action //創建 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操做應該看成 Action,傳給 Store。它能夠是一個函數,也能夠是一個對象 //若是mapDispatchToProps是一個函數,會獲得dispatch和ownProps(容器組件的props對象)兩個參數。 //mapDispatchToProps做爲函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。 //若是mapDispatchToProps是一個對象,它的每一個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被看成 Action creator ,返回的 Action 會由 Redux 自動發出。 const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { // console.log(id) dispatch(toggleTodo(id)) } } } const TodoList = ({ todos, onTodoClick }) => { console.log('9.3 TodoList') // console.log(todos);//todos數組 return ( <ul className='list-group'> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ) } //React-Redux 提供connect方法,用於從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。 //connect方法接受兩個參數:mapStateToProps和mapDispatchToProps。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),後者負責輸出邏輯,即將用戶對 UI 組件的操做映射成 Action。 //connect方法能夠省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引發 UI 組件的更新。 const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) console.log('6. VisibleTodoList end') export default VisibleTodoList;
添加Todo子組件
import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' console.log('7. AddTodo.js start') let AddTodo = ({ dispatch }) => { let input; return ( <div> <form onSubmit={ e => { console.log('data flow 1') e.preventDefault(); if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' } }> <input placeholder='你想作點什麼' ref={r => input = r } className='todo-input' autoFocus={true}/> <button type='submit' className='todo-btn'> 添加任務 </button> </form> </div> ) } AddTodo = connect()(AddTodo)//把addTodo用redux的connect方法從新包裝一下,使其可以使用state中的數據 console.log('7. AddTodo.js end') export default AddTodo;
yarn
或npm install
安裝依賴,運行npm run build
編譯,運行npm start
進行查看頁面和功能是否正常小結:這個階段把頁面和功能實現出來了,初步把整個項目抽離成組件,也利用redux把代碼業務邏輯處理、狀態管理和狀態分發管理分離出來了
總結:本次todolist的項目因時間關係中間斷了兩次,也算是「塞翁失馬」吧!我也把這個項目完整地複習了一遍。這個項目主要把redux的運用以及數據流瞭解了下,算是大概清楚怎麼玩了,爲何說大概呢?一個是由於目前工做中沒有使用,沒法運用到實際項目中,一個就是最外層的index.js,不明白爲何console.log是最後打印出來的,也就意味這是最後加載這個文件的,這個文件個人理解不該該是整個項目js的主入口文件嗎?爲何反而最後加載呢?不明白,先記下來吧,等之後用到redux了再有意識地研究一下。如今把本身作這個項目的詳細步驟、我的理解以及查閱相關的資料整理出來了,不必定十分準確,若是有哪位大神有不一樣的意見歡迎批評指正!最新瞭解了react,很喜歡它的組件化開發,奈何公司用的倒是JQ,因此在react裏我其實還只是個新手,不喜勿噴啊! javascript