todolist for react redux 學習總結

前言

最近一直在學習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配置文件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
  }
}

打開終端運行yarnnpm install安裝依賴,運行npm run build編譯,運行npm start進行查看頁面是否正常顯示

小結:這個階段只是把頁面實現出來了,尚未實現任何邏輯。其中頁面實現的步驟爲:
1. 在index.html編寫html結構和css樣式
2. 把html結構提取到index.js組件中同時轉換成jsx語法
3. 把css樣式提取到app.css中

第二章 引入redux組件化

目錄結構

├── 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"
  }
}

index.js

這次利用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/index.js

把數據邏輯處理的代碼抽離成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
});

actions/index.js

在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')

components/App.js

子組件的入口文件,負責對抽離出的子組件進行組合而後導出
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;

components/Top.js

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;

containers/FilterLink.js

過濾組件
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;

components/Link.js

展現組件,顯示頂部狀態按鈕
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;

components/Todo.js

每個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;

containers/VisibleTodoList.js

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;

containers/AddTodo.js

添加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;

打開終端運行yarnnpm install安裝依賴,運行npm run build編譯,運行npm start進行查看頁面和功能是否正常

小結:這個階段把頁面和功能實現出來了,初步把整個項目抽離成組件,也利用redux把代碼業務邏輯處理、狀態管理和狀態分發管理分離出來了

總結:本次todolist的項目因時間關係中間斷了兩次,也算是「塞翁失馬」吧!我也把這個項目完整地複習了一遍。這個項目主要把redux的運用以及數據流瞭解了下,算是大概清楚怎麼玩了,爲何說大概呢?一個是由於目前工做中沒有使用,沒法運用到實際項目中,一個就是最外層的index.js,不明白爲何console.log是最後打印出來的,也就意味這是最後加載這個文件的,這個文件個人理解不該該是整個項目js的主入口文件嗎?爲何反而最後加載呢?不明白,先記下來吧,等之後用到redux了再有意識地研究一下。如今把本身作這個項目的詳細步驟、我的理解以及查閱相關的資料整理出來了,不必定十分準確,若是有哪位大神有不一樣的意見歡迎批評指正!最新瞭解了react,很喜歡它的組件化開發,奈何公司用的倒是JQ,因此在react裏我其實還只是個新手,不喜勿噴啊! javascript

相關文章
相關標籤/搜索