A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小蜀哥哥 于 2018-10-25 17:07 编辑

react+redux整合开发流程

一、前言


1.1 准备工作
(1) 安装node

(2) 安装git

(3) 安装一款前端IDE --- 本人使用HBuilder
1.2 预备知识
(1) nodejs基本命令
(2) 前端html+javascript+css基础知识
(3) react16相关概念和基本使用
(4) JSX语法
(5) ES6语法
二、react介绍
2.1 Virtual DOM
虚拟DOM是React的基石:(1) 引入虚拟DOM主要是解决Web页面大量操作DOM的性能问题
(2) 在React中,应用程序在虚拟DOM上操作,这让React有了优化的机会
(3) 提供开发服务端应用、Web应用和手机端应用等平台一直的开发方式
2.2 React组件
(1) 所谓组件,即封装起来的具有独立功能的UI部件,React并不是MVC的前端框架  
对于React而言,则完全是一个新的思路,开发者从功能的角度出发,将UI分成不同的组件,每个组件都独立封装
(2) 组件化开发特性:
1.可组合:一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部.通过这个特性,一个复杂的UI可以拆分成多个简单的UI组件
2.可重用:每个组件都是具有独立功能的,它可以被使用在多个UI场景  
3.可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护  4.可测试:因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个UI进行测试容易的多
2.3 Jsx语法
Jsx语法是将HTML语言直接写在JavaScript语言之中,不加任何引号。它允许 HTML 与 JavaScript的混写。  JSX的特点:
1.类XML语法容易接受,结构清晰  
2.增强JS语义  
3.抽象程度高,屏蔽DOM操作,跨平台  
4.代码模块化,组件化,使得每一个组件维护自己的UI
2.4 单向数据流
React是单向数据流,数据主要从父节点传递到子节点(通过props),如果顶层(父级)的某个props改变了,React会重渲染所有的子节点。
三、redux介绍
3.1 传统MVC
传统MVC强调分层开发,model(M)-模型层,view(V)-视图层,controller(C)-控制层,在传统MVC框架中,通常使用双向绑定的方式来将Model的数据展现到View。当Model中的数据发生变化时,一个或多个View会发生变化;当View接受了用户输入时,Model中的数据则会发生变化。如下图所示, Model 和 View 之间的关系错综复杂,导致出现问题时很难调试;实现新功能时也需要时刻注意代码是否会产生副作用
file:////tmp/wps-zhongpeihuan/ksohtml/wpsHGPRZl.jpg
而对于react而言,它自身更强调自己位V-view视图层。同时自身采用单向数据流的思想,此时Flux或者redux状态库管理库就配合react就变得非常完美。

redux流程的逻辑非常清晰,数据流是单向循环的,就像一个生产的流水线:
store(存放状态) -> container(显示状态) -> reducer (处理动作)-> store
3.2 Redux三个概念
1.store:是应用的状态管理中心,保存着是应用的状态(state),当收到状态的更新时,会触发视觉组件进行更新。  
2.container:是视觉组件的容器,负责把传入的状态变量渲染成视觉组件,在浏览器显示出来。  
3.reducer:是动作(action)的处理中心, 负责处理各种动作并产生新的状态(state),返回给store。
四、react+redux整合开发
4.1 环境搭建(1) 安装create-react-app  
[size=12.0000pt] npm install --global create-react-app  
(2) 创建项目
create-react-app react-redux-demo
(3) 在当前工程目录下添加redux依赖
npm install redux --save
npm install react-redux --save
(4) 目录结构  
file:////tmp/wps-zhongpeihuan/ksohtml/wpsKHBLez.jpg
目录介绍:
- public       静态资源存放目录
- img          图片文件夹
- plugins      第三方插件文件夹
- index.html   工程入口访问页面        
- src          源码文件夹:包含项目源码,我们基本都在这个文件夹下做开发
- containers   容器文件夹:存放容器组件
- components   组件文件夹:存放普通显示组件
- actions      actions文件夹:存放可以发出的action
- reducers     reducers文件夹:存放action的处理器reducers
- contants     定义全局常量- app.js       react整合redux数据流入口
- index.js     react应用访问入口
4.2 流程描述
react: 作为视图层,通过dispatcher发送action去触发reducer中的方法来获取redux中store中得状态或者数据进行展示。
redux:
(1) 定义action,通过设定action的type属性和参数
(2) 编写处理器reducer,reducer接收action,根据action的type属性执行相关业务并返回新的state。
(3) 在容器组件里面讲react和redux进行连接。主要是将redux的state和dispatcher绑定到react组件的prop属性下,这样就可以进行向下传递,完成单向数据流
注:store作为redux的数据存储,可以看作数据库。store里面存在很多state,对于一个reducer一般都处理一个state.
4.3 代码实现
1. 创建项目Action常量,在src/contants目录创建actionTypes.js文件
[JavaScript] 纯文本查看 复制代码
//action方法名和actionType常量
export const ADD_BOOK = 'ADD_BOOK'; //添加书籍操作
export const DELETE_BOOK = 'DELETE_BOOK';//删除书籍操作
export const QUERY_BOOK = 'QUERY_BOOK';//查询书籍操作
export const CHECK_ADD = 'CHECK_ADD';//选择删除的记录操作
export const CHECK_REMOVE = 'CHECK_REMOVE';//取消删除操作
export const CHECK_CLEAR = 'CHECK_ALL';//全选
export const CHECK_CLEAR = 'CHECK_CLEAR';//取消全选

2. 创建项目action函数,在src/actions目录下创建action.js
[JavaScript] 纯文本查看 复制代码
import { ADD_BOOK, DELETE_BOOK, QUERY_BOOK,CHECK_ADD,CHECK_REMOVE,CHECK_CLEAR,CHECK_ALL } from "../constants/actionTypes"

/**
 * receiveData和fetchData是绑定的,fetchData是异步action去获取public目录下的data.json
 * 然后发送dispatcher去执行receiveData,最好返回异步加载的data数据
 */
export const receiveData = data => ({ type: 'RECEIVE_DATA', data: data });
export const fetchData = () => {
  return dispatch => {
    fetch('/data.json')
      .then(res => res.json())
      .then(json => dispatch(receiveData(json)));
  };
};

/**
 * addBook新增数据action
 */
export function addBook(book) {
        return {
        type: ADD_BOOK,
        book
        }
}

/**
 *        deleteBook根据ids删除数据
 */
export function deleteBook(ids) {
        return {
        type: DELETE_BOOK,
        ids
        }
}

/**
 * queryBook根据书籍名称查询列表
 */
export function queryBook(bookName) {
        return {
        type: QUERY_BOOK,
        bookName
        }
}
/**
 * checkAdd选择删除的记录id,添加到待删除数组列表
 */
export function checkAdd(id) {
        return {
        type: CHECK_ADD,
        id
        }
}
/**
 * checkRemove从待删除数组列表中取消删除的记录id
 */
export function checkRemove(id) {
        return {
        type: CHECK_REMOVE,
        id
        }
}
/**
 * checkClear将待删除数组列表清空
 */
export function checkClear() {
        return {
                type: CHECK_REMOVE
        }
}
/**
 * 将记录id全部添加到待删除列表
 */
export function checkAll(ids) {
        return {
                type: CHECK_ALL,
                ids
        }
}

3. 创建项目reducers函数
规则:每一个reducer应该维护一种状态或者数据。每一个reducer都是一个独立的js文件。在本案例中存在查询条件、数据列表、待删除数组
3.1) 创建维护待删除数组列表state的js文件,在reducers/bookDeleteState.js文件
[JavaScript] 纯文本查看 复制代码
import { CHECK_ADD ,CHECK_REMOVE,CHECK_CLEAR,CHECK_ALL} from "../constants/actionTypes"
/**
 * 待删除列表state
 *   CHECK_ADD:执行添加待删除记录
 *   CHECK_REMOVE:执行移除待删除记录
 *   CHECK_ALL:全选进入待删除记录
 *   CHECK_CLEAR:清空待删除记录
 */
export default function bookDeleteState(state = [], action) {
    switch(action.type) {
        case CHECK_ADD:
             return  [...state,action.id];
        case CHECK_REMOVE:{ 
                    let state_= [];
                    let j=0;
                    for(var i=0;i<state.length;i++){
                            if(state[i] != action.id){
                                    state_[j++]=state[i]
                            }
                    }
             return state_; }
        case CHECK_ALL:
             return  [...state,action.ids];
        case CHECK_CLEAR:
             return  [];
        default:
            return state;
    }
}

3.2) 创建维护查询条件state的js文件,在reducers/bookQueryState.js文件
[JavaScript] 纯文本查看 复制代码
import { QUERY_BOOK } from "../constants/actionTypes"
/**
* 维护查询条件的state
*   QUERY_BOOK:设置查询条件
*/
export default function bookQueryState(state = "", action) {
    switch(action.type) {
        case QUERY_BOOK:
             return  action.bookName;
        default:
            return state;
    }
}
3.3) 创建数据列表state的js文件,在reducers/bookListState.js文件
[JavaScript] 纯文本查看 复制代码
import { ADD_BOOK, DELETE_BOOK } from "../constants/actionTypes"

/**
* 维护数据列表的State
*         ADD_BOOK:执行新增书籍操作
*  DELETE_BOOK:执行删除书籍的操作
*  RECEIVE_DATA:异步请求,返回查询到的列表
*/
export default function bookListState(state = [], action) {
    switch(action.type) {
        case ADD_BOOK:
             return  [...state, action.book];
        case DELETE_BOOK:{
                   let state_= [];
                  let j=0;
                         for(var i=0;i<state.length;i++){
                            if(action.ids.indexOf(state.id) == -1){
                                    state_[j++]=state
                            }
                    }
                  return  state_;
         } 
        case "RECEIVE_DATA":
            return action.data;
        default:
            return state;
    }
}
3.4) 将redux的reducer合并,并把它们的结果合并成一个state对象,在reducers/reducer.js文件
[JavaScript] 纯文本查看 复制代码
import { combineReducers } from 'redux';
import bookListState from './bookListState';
import bookQueryState from './bookQueryState';
import bookDeleteState from './bookDeleteState';
  
const rootReducer = combineReducers({
        bookListState,
        bookQueryState,
        bookDeleteState
});
  
export default rootReducer; 
3.5) 将使用容器组件将react和redux连接在一起,绑定state数据和dispatcher到react组件的prop  注意:这里将App作为容器组件,因为这里只有一个模块,但是实际情况中存在多个模块,会采用路由的方式进行不同模块的单页跳转,所以应该每一个模块定义一个顶层的容器组件,而不是采用App.js作为容器组件
[JavaScript] 纯文本查看 复制代码
import React, { Component } from 'react';
import { addBook, queryBook,deleteBook,checkAdd,checkRemove,checkClear,checkAll,fetchData } from "./actions/action"
import { connect } from 'react-redux';

import BookItem from "./components/BookItem";
import logo from './logo.svg';

//App作为容器组件
class App extends Component {

   constructor(props) {
      super(props);
      this.saveBook = this.saveBook.bind(this);
      this.queryBook_ = this.queryBook_.bind(this);
     
   }
        componentDidMount() {
                this.props.searchData();    
          }
   saveBook(){
            const book = {
                    id:this.id.value,
                    name:this.name.value,
                    author:this.author.value,
                    price:this.price.value
            };  
            this.props.saveBook(book)
   }  
   queryBook_(name){  
             if(name == "ALL"){
                     this.props.queryBook(""); 
             }else{
                     this.props.queryBook(this.search.value); 
             }
   }
   
  render() {

          let { dispatch,bookList,checkIds,checkAdd,checkRemove,deleteBook } = this.props;
    return (
                ....
                        <form className="navbar-form navbar-left">
                        <div className="form-group">
                          <input type="text" ref={(search)=>{this.search = search}} className="form-control" placeholder="Search" />
                        </div>
                        <button type="button" className="btn btn-default" onClick={this.queryBook_}>Submit</button>
                      </form>
                ....
                                  <button type="button" onClick={()=>{ this.id.value="";this.name.value="";this.author.value="";this.price.value="" }} className="btn btn-default" data-toggle="modal" data-target="#myModal">新增</button>
                                  <button type="button" className="btn btn-default" onClick={this.queryBook_.bind(this,"ALL")}>刷新</button>
                                  <button type="button" className="btn btn-default" onClick={()=>{ deleteBook(checkIds) }}>删除</button>
                ....
                    <table className="table table-striped">
                            <thead>
                                    <th></th>
                                    <th>#</th>
                                         <th>书名</th>
                                         <th>作者</th>
                                         <th>价格</th>
                            </thead>
                            <tbody>
                                    { bookList.map(item => <BookItem item={item} checkAdd={checkAdd} checkRemove={checkRemove}/>) }
                                 </tbody>
                        </table>
                ....
                        <form className="form-horizontal" role="form">
                                <input type="text" ref={(id) => {this.id = id}}  className="form-control" id="id" placeholder="请输入id" />
                                <input type="text" ref={(name) => {this.name = name}} className="form-control" id="lastname" placeholder="请输入书名" />
                                <input type="text" ref={(author) => {this.author = author}} className="form-control" id="lastname" placeholder="请输入作者" />
                                <input type="number" ref={(price) => {this.price = price}}  className="form-control" id="lastname" placeholder="请输入价格" />
                                <button type="button" className="btn btn-primary" data-dismiss="modal" onClick={this.saveBook}>提交保存</button>
                        </form>
                ....
    );
  }
}
//条件过滤
const bookFilter = (bookList ,filter)=>{
        console.log(filter);
    return bookList.filter(item => item.name.indexOf(filter)!=-1);
}
//绑定state数据到容器组件的prop属性
const mapStateToProps = (state) => {
    return {
            //state.bookListState 数据列表
            //state.bookQueryState 过滤条件
        bookList: bookFilter(state.bookListState,state.bookQueryState),
        checkIds: state.bookDeleteState
    }
};

//绑定dispatcher到容器组件的prop属性
const mapDispatchToProps = (dispatch,ownProps) => {
  return {
          searchData:()=>{
                   dispatch(fetchData());
          },
          saveBook:(book)=>{
                   dispatch(addBook(book));
          },
          queryBook:(value)=>{
                  dispatch(queryBook(value));
          },
          deleteBook:(ids)=>{
                  
                  dispatch(deleteBook(ids));
          },
    checkAdd: (id) => {
      dispatch(checkAdd(id));
    },
    checkRemove: (id) => {  
      dispatch(checkRemove(id+""));
    },
    checkClear: () => {
      dispatch(checkClear());
    },
    checkAll: () => {
      dispatch(checkAll());
    }
  };
}
//将绑定后的prop属性连接到容器组件
export default  connect(mapStateToProps,mapDispatchToProps)(App);
3.6) 创建普通组件,创建components/BookItem.js文件
[JavaScript] 纯文本查看 复制代码
import React, {
        Component
} from 'react';

class BookItem extends Component {

        constructor(props) {
            super(props);
            this.checkID=this.checkID.bind(this);
         }

        checkID(event){
                var flag = event.target.checked;
                if(flag){
                        this.props.checkAdd(event.target.value);
                }else{
                        this.props.checkRemove(event.target.value);
                }
        }
        render() {
                let {item} = this.props;
                return(                        
                                <tr>
                                    <td><input type="checkbox" value={item.id} onClick={this.checkID}/></td>
                                         <td>{item.id}</td>
                                         <td>{item.name}</td>
                                         <td>{item.author}</td>
                                         <td>{item.price}</td>
                                 </tr>
                )
        };
}
export default BookItem;
4.4 运行
在dos窗口进入当前工程下执行如下命令启动项目 npm start 效果图[size=12.0000pt]
file:////tmp/wps-zhongpeihuan/ksohtml/wpsqz0E2Y.jpg[size=12.0000pt]


五、总结

本文介绍了react+redux基本概念和思想。同时对于react+redux的整合开发流程做了比较详细的介绍,结合代码以及代码注释阐述了react+redux的思想。就目前前端技术vue、angular、react都各自的思想,个人对于react的思想非常倾佩,vue和angular的双向绑定(MVVC)也非常热衷。希望本文能够对读者有帮助。





0 个回复

您需要登录后才可以回帖 登录 | 加入黑马