dva使用及專案搭建
一、簡介
本文將簡單分析dva腳手架的使用及專案搭建過程。
首先,dva是一個基於redux和redux-saga的資料流方案,然後為了簡化開發體驗,dva還額外內建了react-router和fetch,所以也可以理解為一個輕量級的應用框架。
二、特性
易用易學、elm概念、外掛機制、支援HMR。
三、環境搭建
1、首先安裝dva-cli
npm install dva-cli -g
2、初始化專案
dva new dva-quickstart cd dva-quickstart npm start
3、引入antd
通過 npm 安裝 antd
和 babel-plugin-import
babel-plugin-import
是用來按需載入 antd 的指令碼和樣式的.
npm install antd babel-plugin-import --save
4、按需載入,找到根目錄下面的.webpackrc檔案,並在檔案中新增外掛配置。
"extraBabelPlugins": [ ["import", { "libraryName": "antd", "style": "css" }] ]
5、試引入ant 元件button
import React from 'react'; import { connect } from 'dva'; import styles from'./IndexPage.css'; import { Button } from 'antd' function IndexPage() { return ( <div className={styles.normal}> <h1 className={styles.title}>Yay! Welcome to dva!</h1> <Button type="primary">primary</Button> <div className={styles.welcome} /> <ul className={styles.list}> <li>Getting Started</li> </ul> </div> ); } IndexPage.propTypes= { }; export default connect()(IndexPage);
四、專案目錄結構介紹
1、目錄結構
assets目錄:一般作為靜態檔案儲存目錄,比如圖片或者css;
components:元件目錄;
models:應用邏輯層,可存放公共的資料以及邏輯,類似於vuex;
pages(routes):頁面路由存放資料夾;
services:頁面API請求資料;
utils:公共方法的封裝;
index.js:入口檔案;
router.js:路由檔案
2、具體檔案介紹
2.1、index.js 入口檔案
import dva from 'dva'; import './index.css'; // 1. Initialize const app = dva(); // 2. Plugins // app.use({}); // 3. Model app.model(require('./models/example').default); app.model(require('./models/todos').default); // 4. Router app.router(require('./router').default); // 5. Start app.start('#root');
2.2 router.js路由檔案
import React from 'react'; import { Route, Switch } from 'dva/router'; import dynamic from 'dva/dynamic' // 路由按需載入 import { ConnectedRouter } from 'react-router-redux'; import App from './pages/App' function RouterConfig({ history,app }) { const IndexPage = dynamic({ app, component:(()=> import('./pages/IndexPage/IndexPage')) }) const Users = dynamic({ app, component:(()=> import('./pages/UserPage/UserPage')) }) const List = dynamic({ app, component:(()=> import('./pages/ListPage/ListPage')) }) return ( <ConnectedRouter history={history}> <App> <Switch> <Route path="/" exact component={IndexPage}/> <Route path="/users" exact component={Users}></Route> <Route path="/list" exact component={List}></Route> </Switch> </App> </ConnectedRouter> ); }
2.3 頁面元件IndexPage.js
import React from 'react'; import { connect } from 'dva'; import styles from './IndexPage.css'; import { Button } from 'antd' function IndexPage() { return ( <div className={styles.normal}> <h1 className={styles.title}>Yay! Welcome to dva!</h1> <Button type="primary">primary</Button> <div className={styles.welcome} /> <ul className={styles.list}> <li>Getting Started</li> </ul> </div> ); } IndexPage.propTypes = { }; export default connect()(IndexPage);
五、connect()方法介紹
connect 是一個函式,繫結 State 到 View。
import { connect } from 'dva'; function mapStateToProps(state) { return { todos: state.todos }; } connect(mapStateToProps)(App);
connect 方法返回的也是一個 React 元件,通常稱為容器元件。因為它是原始 UI 元件的容器,即在外面包了一層 State。
connect 方法傳入的第一個引數是 mapStateToProps 函式,mapStateToProps 函式會返回一個物件,用於建立 State 到 Props 的對映關係。
六、dispatch方法
dispatch 是一個函式方法,用來將 Action 傳送給 State。
dispatch({ type: 'click-submit-button', payload: this.form.data })
type:方法名;
payload:引數
dispatch 方法從哪裡來?被 connect 的 Component 會自動在 props 中擁有 dispatch 方法。
七、model層介紹
比較常用的model成如下
{ namespace: 'count', state: 0, reducers: { add(state) { return state + 1 }, }, effects: { *addAfter1Second(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'add' }); }, }, }
1.namespace:名稱空間;當前 Model 的名稱。整個應用的 State,由多個小的 Model 的 State 以 namespace 為 key 合成
2.state:該 Model 當前的狀態。資料儲存在這裡,直接決定了檢視層的輸出
3.reducers: Action 處理器,處理同步動作,用來算出最新的 State;
4.effects:Action 處理器,處理非同步動作
注:函式名前邊帶一個*號,是一個生成器(Generator )函式,內部使用 yield 關鍵字,標識每一步的操作(不管是非同步或同步)。
dva 提供多個 effect 函式內部的處理函式,比較常用的是 call
和 put
。
call:執行非同步函式
put:發出一個 Action,類似於 dispatch
八、demo TODOLIst 實現
1.首先在components下新建一個TodoList.js檔案
import React from 'react'; class TodoList extends React.Component{ constructor(props) { super(props); this.state={ value:'' } } addTodo(e){ if (e.keyCode===13) { const todo = e.target.value; this.props.dispatch({ type: 'todos/addTodo', payload: todo }) this.setState({ value: '' }) } } deleteTodo(index){ this.props.dispatch({ type: 'todos/deleteTodo', payload: index }) } render() { const todoList = this.props.todoList.map((val, index) => { return <div key={index}> <span>{val.value}</span> <button onClick={() => this.deleteTodo(index)}>X</button> </div> }); let count = 0; this.props.todoList.map(item => count = !item.finished ? count + 1 : count); return ( <div> <h3>待辦事項有:{count}</h3> <input placeholder="please input" value={this.state.value} onChange={(e) => this.setState({value: e.target.value})} onKeyDown={(e) => this.addTodo(e)}/> <div> {todoList} </div> </div> ) } } export default TodoList;
程式碼中:通過dispatch 派送一個action,type為action名稱,payload為傳遞引數
this.props.dispatch({ type: 'todos/addTodo', payload: todo })
2.新建路由頁面LIstPage.js
import {connect} from 'dva'; import TodoList from '../../components/TodoList'; const mapStateToProps = (state) => { return { todoList: state.todos.todoList } }; export default connect(mapStateToProps)(TodoList);
通過mapStateToProps 方法將model裡的todoList放回到頁面元件的props.todoList;
3.新建一個model todos.js
import queryString from 'query-string'; import * as todoService from '../services/todo' export default { namespace: 'todos', state: {todoList: []}, reducers: { save(state, {payload: {todoList}}) { return {...state, todoList} } }, effects: { * addTodo({payload: value}, {call, put, select}) { // 模擬網路請求 const data = yield call(todoService.query, value); let tempList = yield select(state => state.todos.todoList); let todoList = []; todoList = todoList.concat(tempList); const tempObj = {}; tempObj.value = value; tempObj.id = todoList.length; todoList.push(tempObj); yield put({type: 'save', payload: {todoList}}) }, * deleteTodo({payload: index}, {call, put, select}) { const data = yield call(todoService.query, index); let tempList = yield select(state => state.todos.todoList); let todoList = []; todoList = todoList.concat(tempList); todoList.splice(index, 1); yield put({type: 'save', payload: {todoList}}) }, }, subscriptions: { setup({dispatch, history}) { // 監聽路由的變化,請求頁面資料 return history.listen(({pathname, search}) => { const query = queryString.parse(search); let todoList = []; if (pathname === 'todos') { dispatch({type: 'save', payload: {todoList}}) } }) } } }
一般來說,effects做主要的邏輯計算,reducers做資料儲存,通過複雜的邏輯計算後,把處理好的資料呼叫reducers的方法進行資料儲存。
4.在index.js進行model以及路由注入
import dva from 'dva'; import './index.css'; // 1. Initialize const app = dva(); // 2. Plugins // app.use({}); // 3. Model app.model(require('./models/example').default); app.model(require('./models/todos').default); // 4. Router app.router(require('./router').default); // 5. Start app.start('#root');