资讯专栏INFORMATION COLUMN

React-Redux小应用(一)-React_Redux_Appointment

CoorChice / 429人阅读

摘要:先来一波硬广我的博客欢迎观光传送门这个小应用使用创建演示地址,地址。这是之前的的版,之前的演示,改写自的课程。

React-Redux-Appointment

先来一波硬广:我的博客欢迎观光:传送门
这个小应用使用Create React App创建,演示地址:https://liliang-cn.github.io/react_redux_appointment,repo地址:https://github.com/liliang-cn/react_redux_appointment。

这是之前的React_appointment的Redux版,之前的演示,改写自Lynda的课程Building a Web Interface with React.js。

文件结构

最终的文件目录如下:

react_redux_appointment/
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    actions/
      index.js
    components/
      AddForm.js
      AptList.js
      Search.js
      Sort.js
    constants/
      index.js
    containers/
      AddForm.js
      App.js
    reducers/
      apts.js
      formExpanded.js
      index.js
      openDialog.js
      orderBy.js
      orderDir.js
      query.js
    index.css
    index.js
用到的模块
{
  "name": "react_redux_appointment",
  "version": "0.1.0",
  "private": true,
  "homepage": "https://liliang-cn.github.io/react_redux_appointment",
  "devDependencies": {
    "react-scripts": "0.8.4"
  },
  "dependencies": {
    "axios": "^0.15.3",
    "gh-pages": "^0.12.0",
    "lodash": "^4.17.2",
    "material-ui": "^0.16.5",
    "moment": "^2.17.1",
    "react": "^15.4.1",
    "react-dom": "^15.4.1",
    "react-redux": "^5.0.1",
    "react-tap-event-plugin": "^2.0.1",
    "redux": "^3.6.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "deploy": "yarn build && gh-pages -d build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
所有的state

小应用一共有六个状态,其中的formExpanded和openDialog是界面组件的状态,
剩下的四个分别是apts(代表所有的预约)、orderBy(根据什么来排列预约列表,根据姓名还是根据日期)、
orderDir(排列列表的方向,是增序还是降序)、query(搜索的关键字)。

所有的Action

在应用中可能产生的actions有七种:

addApt,即新建预约

deleteApt, 即删除预约

toggleDialog, 即显示、隐藏警告框

toggleFormExpanded, 显示/隐藏表单

query,即查询

changeOrderBy,即改变排序的关键字

changeOrderDir, 即改变排序方向

定义七个常量来代表这些action的类型:

constants/index.js:

export const ADD_APT = "ADD_APT";

export const DELETE_APT = "DELETE_APT";

export const TOGGLE_DIALOG = "TOGGLE_DIALOG";

export const TOGGLE_FORM_EXPANDED = "TOGGLE_FORM_EXPANDED";

export const QUERY = "QUERY";

export const CHANGE_ORDER_BY = "CHANGE_ORDER_BY";

export const CHANGE_ORDER_DIR = "CHANGE_ORDER_DIR";

actions/index.js:

import {
    ADD_APT,
    DELETE_APT,
    TOGGLE_DIALOG,
    TOGGLE_FORM_EXPANDED,
    QUERY,
    CHANGE_ORDER_BY,
    CHANGE_ORDER_DIR
} from "../constants";

export const addApt = (apt) => ({
    type: ADD_APT,
    apt
});

export const deleteApt = (id) => ({
    type: DELETE_APT,
    id
});

export const toggleDialog = () => ({
    type: TOGGLE_DIALOG
});

export const toggleFormExpanded = () => ({
    type: TOGGLE_FORM_EXPANDED
});

export const query = (query) => ({
    type: QUERY,
    query
});

export const changeOrderBy = (orderBy) => ({
    type: CHANGE_ORDER_BY,
    orderBy
});

export const changeOrderDir = (orderDir) => ({
    type: CHANGE_ORDER_DIR,
    orderDir
});
UI组件 样式

使用Material-UI需要引入Roboto字体:

src/index.css

@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500");
body {
  margin: 0;
  padding: 0;
  font-family: Roboto, sans-serif;
}
表单组件

components/addForm.js:

import React from "react";

import {Card, CardHeader, CardText} from "material-ui/Card";
import TextField from "material-ui/TextField";
import DatePicker from "material-ui/DatePicker";
import TimePicker from "material-ui/TimePicker";
import RaisedButton from "material-ui/RaisedButton";
import Paper from "material-ui/Paper";
import Divider from "material-ui/Divider";
import Dialog from "material-ui/Dialog";
import FlatButton from "material-ui/FlatButton";

import moment from "moment";

const paperStyle = {
  width: 340,
  margin: "0 auto 20px",
  textAlign: "center"
};

const buttonStyle = {
    margin: 12
};

// open, toggleDialog是两个布尔值,handleAdd,formExpanded, toggleFormExpanded是三个回调函数,来自于../containers/AddForm.js中的容器从store中获取并传递下来的
const AddForm = ({handleAdd, open, toggleDialog, formExpanded, toggleFormExpanded}) => {
    let guestName, date, time, note;
    // 点击Add时会先首先检查是否所有的值都有输入,如果输入合法则发起ADD_APT的action然后发起切换表单显示的action,如果输入有误则发起TOGGLE_DIALOG的action
    const onAdd = () => {
        guestName && date && time && note
        ?
        handleAdd({guestName, date, time, note}) && toggleFormExpanded()
        :
        toggleDialog()
    };

    // 这两个函数用来获取输入的日期和时间
    const handleDateChange = (event, aptDate) => {
        date = moment(aptDate).format("YYYY-MM-DD")
    };

    const handleTimeChange = (event, aptTime) => {
        time = moment(aptTime).format("hh:mm")
    };

    const actions = [
        
    ];

    return (
        
            // Card组件的expanded的值是一个布尔值,来自于父组件传下来的formExpanded,即应用的状态formExpanded,用来确定是否显示表单
            
                
                
                     guestName = e.target.value.trim()}
                    />
                    
                    
                    
                    
                    
                     note = e.target.value.trim()}
                    />
                    
                    
                    
                
                // Dialog组件的open的值也是一个布尔值,来自于父组件传下来的open,即应用的状态openDialog,用来验证表单
                
                    All fileds are required!
                
            
        
    );
};

export default AddForm;
搜索表单

components/Search.js:

import React from "react";
import TextField from "material-ui/TextField";

const Search = ({handleSearch}) => {
    return (
        
handleSearch(e.target.value) } />
); }; export default Search;
排列选择

components/Sort.js:

import React from "react";

import SelectField from "material-ui/SelectField";
import MenuItem from "material-ui/MenuItem"

const Sort = ({
    orderBy,
    orderDir,
    handleOrderByChange,
    handleOrderDirChange
}) => {
    return (
        
{handleOrderByChange(value)}} > {handleOrderDirChange(value)}} >
); }; export default Sort;
预约列表

这个组件的作用就是显示预约列表,接受父组件传来的apts数组和handleDelete函数,在点击RaisedButton的时候将apt.id传入handleDelete并执行。

components/AptList.js:

import React from "react";
import {List, ListItem} from "material-ui/List";
import {Card, CardActions, CardHeader, CardTitle, CardText} from "material-ui/Card";
import RaisedButton from "material-ui/RaisedButton";

const buttonStyle = {
    width: "60%",
    margin: "12px 20%",
};

const AptList = ({apts, handleDelete}) => {
    return (
        

Appointments List

// 这里的i也可以直接用apt.id {apts.map((apt, i) => ( {apt.note} handleDelete(apt.id)} /> ))}
); }; export default AptList;
处理不同的actions 处理表单的显示和隐藏

reducers/formExpanded.js:

import { TOGGLE_FORM_EXPANDED } from "../constants";

// formExpanded默认为false,即不显示,当发起类型为TOGGLE_FORM_EXPANDED的action的时候,将状态切换为true或者false
const formExpanded = (state=false, action) => {
    switch (action.type) {
        case TOGGLE_FORM_EXPANDED:
            return !state;
        default:
            return state;
    }
};

export default formExpanded;
表单验证错误的提示对话框

reducers/openDialog.js:

import { TOGGLE_DIALOG } from "../constants";

// 这个action是由其他action引发的
const openDialog = (state=false, action) => {
    switch (action.type) {
        case TOGGLE_DIALOG:
            return !state;
        default:
            return state;
    }
};

export default openDialog;
处理新建预约和删除预约

reducers/apts.js:

import { ADD_APT, DELETE_APT } from "../constants";

// 用唯一的id来标识不同的预约,也可以直接用时间戳new Date()
let id = 0;

// 根据传入的数组和id来执行删除操作
const apts = (state=[], action) => {
    const handleDelete = (arr, id) => {
        for(let i=0; i
查询和排列方式

这三个函数的作用就是根据action传入的数据,更新state里的对应值,在这里并不会真正的去处理预约的列表。

reducers/orderBy.js:

import { CHANGE_ORDER_BY } from "../constants";

const orderBy = (state=null, action) => {
    switch (action.type) {
        case CHANGE_ORDER_BY:
            return action.orderBy
        default:
            return state;
    }
};

export default orderBy;

reducers/orderDir.js:

import { CHANGE_ORDER_DIR } from "../constants";

const orderDir = (state=null, action) => {
    switch (action.type) {
        case CHANGE_ORDER_DIR:
            return action.orderDir
        default:
            return state;
    }
};

export default orderDir;

reducers/query.js:

import { QUERY } from "../constants";

const query = (state=null, action) => {
    switch (action.type) {
        case QUERY:
            return action.query;
        default:
            return state;
    }
}

export default query;
合成reducers

reducers/index.js:

import { combineReducers } from "redux";

import apts from "./apts";
import openDialog from "./openDialog";
import formExpanded from "./formExpanded";
import query from "./query";
import orderBy from "./orderBy";
import orderDir from "./orderDir";

// redux提供的combineReducers函数用来将处理不同部分的state的函数合成一个
// 每当action进来的时候会经过每一个reducer函数,但是由于action类型(type)的不同
// 只有符合(switch语句的判断)的reducer才会处理,其他的只是将state原封不动返回

const reducers = combineReducers({
    apts,
    openDialog,
    formExpanded,
    query,
    orderBy,
    orderDir
});

export default reducers;
容器组件

containers/AddForm.js:

import { connect } from "react-redux";

import { addApt, toggleDialog, toggleFormExpanded } from "../actions";

import AddForm from "../components/AddForm";

// AddForm组件可通过props来获取两个state:open和formExpanded
const mapStateToProps = (state) => ({
    open: state.openDialog,
    formExpanded: state.formExpanded
});

// 使得AddForm组件可以通过props得到三个回调函数,调用即可相当于发起action
const mapDispatchToProps = ({
    toggleFormExpanded,
    toggleDialog,
    handleAdd: newApt => addApt(newApt)
});

// 使用react-redux提供的connect函数,可以将一个组件提升为容器组件,容器组件可直接获取到state、可以直接使用dispatch。
// 这个connect函数接受两个函数作为参数,这两个作为参数的函数的返回值都是对象, 按约定他们分别命名为mapStateToProps,mapDispatchToProps
// mapStateToProps确定了在这个组件中可以获得哪些state,这里的话只用到了两个UI相关的state:open和formExpanded,这些state都可通过组件的props来获取
// mapDispatchToProps本来应该是返回对象的函数,这里比较简单,直接写成一个对象,确定了哪些action是这个组件可以发起的,也是通过组件的props来获取
// connect函数的返回值是一个函数,接受一个组件作为参数。

export default connect(mapStateToProps, mapDispatchToProps)(AddForm);

containers/App.js:

import React from "react";
import { connect } from "react-redux";

import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"
import injectTapEventPlugin from "react-tap-event-plugin";

injectTapEventPlugin();
import AppBar from "material-ui/AppBar";
import Paper from "material-ui/Paper";

import AddForm from "../containers/AddForm";
import Search from "../components/Search";
import Sort from "../components/Sort";
import AptList from "../components/AptList";

import { deleteApt, query, changeOrderBy, changeOrderDir } from "../actions";

const paperStyle = {
  minHeight: 600,
  width: 360,
  margin: "20px auto",
  textAlign: "center"
};

const App = ({
  apts,
  dispatch,
  orderBy,
  orderDir,
  handleSearch,
  handleDelete,
  handleOrderByChange,
  handleOrderDirChange
}) => (
  
    
); // 处理搜索和排序,返回处理后数组 const handledApts = (apts, query, orderBy, orderDir) => { const filterArr = (arr, query) => { return arr.filter(item => ( item.guestName.toLowerCase().indexOf(query) !== -1 || item.date.indexOf(query) !== -1 || item.time.indexOf(query) !== -1 || item.note.toLowerCase().indexOf(query) !== -1) ); }; const sortArr = (arr, orderBy, orderDir) => { if (orderBy && orderDir) { return arr.sort((apt1, apt2) => { const value1 = apt1[orderBy].toString().toLowerCase(); const value2 = apt2[orderBy].toString().toLowerCase(); if (value1 < value2) { return orderDir === "asc" ? -1 : 1; } else if (value1 > value2) { return orderDir === "asc" ? 1 : -1; } else { return 0; } }) } else { return arr; } }; if (!query) { return sortArr(apts, orderBy, orderDir); } else { return sortArr(filterArr(apts, query), orderBy, orderDir); } }; // App组件可通过props来获取到四个state:query, orderBy, orderDir, apts // 这里是真正处理搜索和排序的地方,并不是直接将state中的apts返回,而是调用handleApts,返回处理的数组 const mapStateToProps = (state) => ({ query: state.query, orderBy: state.orderBy, orderDir: state.orderDir, apts: handledApts(state.apts, state.query, state.orderBy, state.orderDir), }); // App组件可通过props来获取到四个函数,也就是发起四个action:handleSearch,handleDelete,handleOrderByChange,handleOrderDirChange const mapDispatchToProps = ({ handleSearch: searchText => query(searchText), handleDelete: id => deleteApt(id), handleOrderByChange: orderBy => changeOrderBy(orderBy), handleOrderDirChange: orderDir => changeOrderDir(orderDir) }); export default connect(mapStateToProps, mapDispatchToProps)(App);
入口文件

src/index.js:

import React from "react";
import ReactDOM from "react-dom";

import { createStore } from "redux";
import { Provider } from "react-redux";

import App from "./containers/App";
import "./index.css";

import reducers from "./reducers";

// 使用createStore表示应用的store,传入的第一个参数是reducers,第二个参数是Redux的调试工具
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

// 使用react-redux提供的Provider组件,使App组件及子组件可以得到store的相关的东西,如store.getState(),store.dispatch()等。
ReactDOM.render(
  
    
  ,
  document.getElementById("root")
);
结尾

React提供的是通过state来控制控制UI和单向数据流动,
Redux提供的是单一数据源和只能通过action和reducer来处理state的更新。

以其中的点击按钮显示新建预约表单的过程来捋一捋React、React-Redux的逻辑(灵感来源于自Cory House大神):

用户:点击按钮

React:哈喽,action生成函数toggleFormExpanded,有人点击了展开新建预约的表单。

Action:收到,谢谢React,我马上发布一个action也就是{type:TOGGLE_FORM_EXPANDED}告诉reducers来更新state。

Reducer:谢谢Action,我收到你的传过来要执行的action了,我会根据你传递进来的{type:TOGGLE_FORM_EXPANDED},先复制一份当前的state,然后把state中的formExpanded的值更新为true,然后把新的state给Store。

Store:嗯,Reducer你干得漂亮,我收到了新的state,我会通知所有与我连接的组件,确保他们会收到新state。

React-Redux:啊,感谢Store传来的新数据,我现在就看看React界面是否需要需要发生变化,啊,需要把新建预约的表单显示出来啊,那界面还是要更新一下的,交给你了,React。

React:好的,有新的数据由store通过props传递下来的数据了,我会马上根据这个数据把新建预约的表单显示出来。

用户:看到了新建预约的表单。

如果觉得还不错,来个star吧。(笑脸)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86631.html

相关文章

  • react+react-router+react-redux全家桶项目开发过程分享

    摘要:项目地址下载完项目然后即可基于的项目,主要是为了学习实战。数据都是固定的,从饿了么接口临时抓的,模拟了一个的异步数据延迟,感谢饿了么。详细信息可以看上面的官方文档,我这里就简单说一下我这个项目的应用。 react-ele-webapp 项目地址 :https://github.com/kliuj/reac... run 下载完项目npm install然后npm run dev 即可 ...

    zzir 评论0 收藏0
  • React-Redux性能优化

    摘要:但是和一起使用还需要一个工具,这一篇就说一下在使用上的一些性能优化建议。如果的改变会引起值变化,那么会调用转换函数,传入作为参数,并返回结果。如果的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。 前面写了两篇文章《React组件性能优化》《Redux性能优化》,分别针对React和Redux在使用上的性能优化给了一些建议。但是React和Redux一起使用...

    JouyPub 评论0 收藏0
  • react、react-router、redux 也许是最佳实践2

    摘要:上一篇也许是最佳小实践加入在组件之间流通数据更确切的说,这被叫做单向数据流数据沿着一个方向从父组件流到子组件。这个将这个新的对象附加到上,并将它返回,用来更新。这一次,将当前的状态仍旧是空数组和对象一起传递给了。 上一篇:react、react-router、redux 也许是最佳小实践1 加入 redux React 在组件之间流通数据.更确切的说,这被叫做单向数据流——数据沿着一个...

    AaronYuan 评论0 收藏0
  • React-redux基础

    摘要:简介创建的函数,返回一个对象,包含等方法合并多个中间件处理,在实际的前调用一系列中间件,类似于绑定和函数式编程中常见的方法,介绍官方提供的绑定库。 前言 在学习了React之后, 紧跟着而来的就是Redux了~ 在系统性的学习一个东西的时候, 了解其背景、设计以及解决了什么问题都是非常必要的。接下来记录的是, 我个人在学习Redux时的一些杂七杂八~ Redux是什么 通俗理解 h...

    jsyzchen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<