实例解说react+react-router+redux

媒介

总括: 本文采纳react+redux+react-router+less+es6+webpack,以完成一个浅易备忘录(todolist)为例尽量周全的报告运用react百口桶完成一个完全运用的历程。

人生不失意,焉能暴己知。

手艺申明

手艺架构:本备忘录运用react+react-router+redux+less+ES6+webpack完成;

页面UI参照:TodoList官网完成;

在线演示地点:Damonare的备忘录;

功用申明

  • 支撑回车增加新事项;

  • 支撑删除事项(点击X标记);

  • 支撑状况转换详细包括:

    • 新建事项->正在举行(点击checkbox选项)

    • 正在举行->已完成(点击笔墨内容自身)

    • 正在举行->新建事项(点击checkbox选项)

    • 已完成->正在举行(点击笔墨自身)

  • 支撑推断输入空字符,太长字符(20个汉字之内);

  • 支撑搜刮;

  • 支撑当地化存储;

  • 支撑状况的睁开隐蔽(点击题目)

  • 兼容手机端(iPhone6及以上)

  • 支撑路由切换

正文

1. React浅谈

1.1 组件化

​ 毫无疑问,当谈到React的时刻不能防备的会提到组件化头脑。React刚开始想处理的题目只是UI这一层面的题目,也就是MVC中view层面的题目,不成想如今越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 处理计划。关于React组件的邃晓一样要站在view层面的角度动身,一个完全的页面是由大大小小的组件堆叠而成,就好像搭积木,每一块积木都是一个组件,组件套组件构成了用户所能看到的完全的页面。

1.2 JSX语法糖

​ 运用React,不一定非要运用JSX语法,能够运用原生的JS举行开辟。然则React作者强烈建议我们运用JSX,由于JSX在定义相似HTML这类树形构造时,非常的简朴明了。这里简朴的讲下JSX的由来。

​ 比方,下面一个div元素,我们用HTML语法形貌为:

<div class="test">
  <span>Test</span>
</div>

假如换做运用javascript形貌这个元素呢?最好的体式格局能够简朴的转化为json对象,以下:

{
  type:"div",
  props:{
    className:"test",
    children:{
      type:"span",
      props:{
        children:"Test"
      }
    }
  }
}

如许我们就能够在javascript中建立一个Virtual DOM(假造DOM)了。固然,如许是没法复用的,我们再把它封装一下:

const Div=>({text}){
  return {
    type:"div",
    props:{
      className:"test",
      children:{
        type:"span",
        props:{
          children: text,
        },
      },
    },
  }
}

接下来再完成这个div就能够直接挪用Div(‘Test’)来建立。但上述构造看起来着实让人不爽,写起来也很轻易写混,一旦构造庞杂了,很轻易让人找不着北,因而JSX语法应运而生。我们用写HTML的体式格局写这段代码,再经由翻译器转换成javascript后交给浏览器实行。上述代码用JSX重写:

const Div =()=>(
<div className="test">
  <span>Test</span>
</div>
);

何等简朴明了!!!详细的JSX语法不多说了,进修更多戳这:JSX in Depth

1.3 Virtual DOM

实在上面已提到了Virtual DOM,它的存在也是React长久不衰的缘由之一,假造DOM的观点并非FB开创却在FB的手上大火了起来(背景是何等重要)。

我们晓得实在的页面对应了一个DOM树,在传统页面的开辟情势中,每次须要更新页面时,都须要对DOM举行更新,DOM操纵非常高贵,为削减关于实在DOM的操纵,诞生了Virtual DOM的观点,也就是用javascript把实在的DOM树形貌了一遍,运用的也就是我们方才说过的JSX语法。对照方下:

《实例解说react+react-router+redux》

每次数据更新今后,从新盘算Virtual DOM,并和上一次的Virtual DOM对照,对发作的变化举行批量更新。React也供应了shouldComponentUpdate生命周期回调,来削减数据变化后不必要的Virtual DOM对照历程,提升了机能。

Virtual DOM虽然衬着体式格局比传统的DOM操纵要好一些,但并不显著,由于对照DOM节点也是须要盘算的,最大的优点在于能够很轻易的和别的平台集成,比方react-native就是基于Virtual DOM衬着出原生控件。详细衬着出的是Web DOM照样Android控件或是iOS控件就由平台决议了。所以我们说react的出现是一场反动,一次关于native app的宣战,就像react-native那句标语——Learn Once,Write Anywhere.

1.4 函数式编程

​ 过去编程体式格局重如果以敕令式编程为主,什么意义呢?简朴说电脑的头脑体式格局和我们人类的思索体式格局是不一样的。我们人类的大脑善于的是剖析题目,提出一个处理题目的计划,电脑则是僵硬的实行指令,敕令式编程就像是给电脑下达敕令,让电脑去实行一样,如今重要的编程言语(比方:Java,C,C++等)都是由敕令式编程构建起来的。

​ 而函数式编程就不一样了,这是模拟我们人类的头脑体式格局发现出来的。比方:操纵某个数组的每一个元素然后返回一个新数组,假如是盘算机的思索体式格局,会如许想:建立一个新数组=>遍历旧数组=>给新数组赋值。假如是人类的思索体式格局,会如许想:建立一个数组要领,作用在旧数组上,返回新数组。如许此要领能够被反复应用。而这就是函数式编程了。

1.5 数据流

在React中,数据的运动是单向的,即从父节点通报到子节点。也因而组件是简朴的,他们只须要从父组件猎取props衬着即可。假如顶层的props转变了,React会递归的向下遍历全部组件树,从新衬着统统运用这个属性的组件。那末父组件怎样猎取子组件数据呢?很简朴,经由过程回调就能够了,父组件定义某个要领供应子组件挪用,子组件挪用要领通报给父组件数据,Over。

2. React-router

这东西我以为没啥难度,官方例子都很不错,跟着官方例子来一遍基础就邃晓究竟是个啥玩意了,官方例子:react-router-tutorial。

完事今后能够再看一下阮一峰先生的教程,重如果对一些API的解说:React Router 运用教程

另有啥不邃晓的迎接批评留言配合讨论。

3. Redux

3.1 简介

跟着 JavaScript 单页运用开辟日益庞杂,JavaScript 须要治理比任何时刻都要多的 state (状况)。 这些 state 能够包括服务器相应、缓存数据、当地天生还没有耐久化到服务器的数据,也包括 UI 状况,如激活的路由,被选中的标签,是不是显现加载动效或许分页器等等。假如一个 model 的变化会引发另一个 model 变化,那末当 view 变化时,就能够引发对应 model 以及另一个 model 的变化,顺次地,能够会引发另一个 view 的变化。乱!

这时刻Redux就强势上台了,如今你能够把React的model看作是一个个的子民,每一个子民都有本身的一个状况,纷纷扰扰,各自保护着本身状况,我行我素,那哪行啊!太乱了,我们须要一个King来指导人人,我们就能够把Redux看作是这个King。收罗统统的组件构成一个国度,掌控着统统子民的状况!防备有人兵变惹事!

这个时刻就把组件分成了两种:容器组件(King或是路由)和展示组件(子民)。

  • 容器组件:即redux或是router,起到了保护状况,动身action的作用,实在就是King居高临下下达指令。

  • 展示组件:不保护状况,统统的状况由容器组件经由过程props传给他,统统操纵经由过程回调完成。

展示组件容器组件
作用形貌怎样展示(骨架、款式)形貌怎样运转(数据猎取、状况更新)
直接运用 Redux
数据泉源props监听 Redux state
数据修正从 props 挪用回调函数向 Redux 派发 actions
挪用体式格局手动一般由 React Redux 天生

Redux三大部分:store,action,reducer。相当于King的直系部属。

那末也能够看出Redux只是一个状况治理计划,完全能够零丁拿出来运用,这个King不仅仅能够是React的,去Angular,Ember那边也是能够做King的。在React中维系King和组件关联的库叫做 react-redux

, 它重要有供应两个东西:Provider connect,详细运用文后申明。

供应几个Redux的进修地点:官方教程-中文版Redux 入门教程(一):基础用法

3.2 Store

Store 就是保留数据的处所,它实际上是一个Object tree。全部运用只能有一个 Store。这个Store能够看作是King的宰衡,掌控统统子民(组件)的运动(state)。

Redux 供应createStore这个函数,用来天生 Store。

import { createStore } from 'redux';
const store = createStore(func);

createStore接收一个函数作为参数,返回一个Store对象(宰衡诞生记)

我们来看一下Store(宰衡)的职责:

3.3 action

State 的变化,会致使 View 的变化。然则,用户打仗不到 State,只能打仗到 View。所以,State 的变化必需是 View 致使的。Action 就是 View 发出的关照,示意 State 应当要发作变化了。即store的数据变化来自于用户操纵。action就是一个关照,它能够看作是宰衡下面的邮递员,关照子民(组件)转变状况。它是 store 数据的唯一泉源。一般来说会经由过程 store.dispatch() 将 action 传到 store。

Action 是一个对象。个中的type属性是必需的,示意 Action 的称号。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

Action建立函数

Action 建立函数 就是天生 action 的要领。“action” 和 “action 建立函数” 这两个观点很轻易混在一同,运用时最好注重辨别。

在 Redux 中的 action 建立函数只是简朴的返回一个 action:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

如许做将使 action 建立函数更轻易被移植和测试。

3.4 reducer

Action 只是形貌了有事变发作了这一现实,并没有指明运用怎样更新 state。而这恰是 reducer 要做的事变。也就是邮递员(action)只担任关照,详细你(组件)怎样去做,他不担任,这事变只能是你们村长(reducer)通知你怎样去做才相符社会主义中心价值观,怎样做才对建立共产主义社会有益。

专业诠释: Store 收到 Action 今后,必需给出一个新的 State,如许 View 才会发作变化。这类 State 的盘算历程就叫做 Reducer。

Reducer 是一个函数,它接收 Action 和当前 State 作为参数,返回一个新的 State。

const reducer = function (state, action) {
  // ...
  return new_state;
};

3.5 数据流

严厉的单向数据流是 Redux 架构的设想中心。

Redux 运用中数据的生命周期遵照下面 4 个步骤:

  • 挪用 store.dispatch(action)

  • Redux store 挪用传入的 reducer 函数。

  • 根 reducer 应当把多个子 reducer 输出合并成一个单一的 state 树。

  • Redux store 保留了根 reducer 返回的完全 state 树

事情流程图以下:

《实例解说react+react-router+redux》

3.6 Connect

这里须要再强调一下:Redux 和 React 之间没有关联。Redux 支撑 React、Angular、Ember、jQuery 以至纯 JavaScript。

尽管如此,Redux 照样和 React 和 Deku 这类框架搭配起来用最好,由于这类框架许可你以 state 函数的情势来形貌界面,Redux 经由过程 action 的情势来提议 state 变化。

Redux 默许并不包括 React 绑定库,须要零丁装置。

npm install --save react-redux

固然,我们这个实例里是不须要的,统统须要的依靠已在package.json里设置好了。

React-Redux 供应connect要领,用于从 UI 组件天生容器组件。connect的意义,就是将这两种组件连起来。

import { connect } from 'react-redux';
const TodoList = connect()(Memos);

上面代码中Memos是个UI组件,TodoList就是由 React-Redux 经由过程connect要领自动天生的容器组件。

而只是地道的如许把Memos包裹起来毫无意义,完全的connect要领如许运用:

import { connect } from 'react-redux'
const TodoList = connect(
  mapStateToProps
)(Memos)

上面代码中,connect要领接收两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的营业逻辑。前者担任输入逻辑,行将state映射到 UI 组件的参数(props),后者担任输出逻辑,行将用户对 UI 组件的操纵映射成 Action。

3.7 Provider

 这个Provider 实际上是一个中间件,它是为了处理让容器组件拿到King的指令(state对象)而存在的。

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

上面代码中,Provider在根组件表面包了一层,如许一来,App的统统子组件就默许都能够拿到state了。

4.实战备忘录

解说之前能够先看一下github上的代码,你能够clone下来进修,也能够在线给我提issue,迎接戳这:React百口桶完成浅易备忘录

4.1目次构造

.
├── app                 #开辟目次
|   |   
|   ├──actions          #action的文件
|   |   
|   ├──components       #展示组件
|   |   
|   ├──containers       #容器组件,主页
|   |   
|   ├──reducers         #reducer文件
|   |
|   |——routes           #路由文件,容器组件
|   |
|   |——static           #静态文件
|   |
|   ├──stores           #store设置文件
|   |
|   |——main.less        #路由款式
|   |
|   └──main.js          #进口文件
|      
├── build                #宣布目次
├── node_modules        #包文件夹
├── .gitignore     
├── .jshintrc      
├── webpack.production.config.js  #临盆环境设置      
├── webpack.config.js   #webpack设置文件
├── package.json        #环境设置
└── README.md           #运用申明

接下来,我们只关注app目次就好了。

4.2进口文件

import React from 'react';
import ReactDOM from 'react-dom';
import {Route, IndexRoute, browserHistory, Router} from 'react-router';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import App from './container/App';
import AllMemosRoute from './routes/AllMemosRoute';
import TodoRoute from './routes/TodoRoute';
import DoingRoute from './routes/DoingRoute';
import DoneRoute from './routes/DoneRoute';
import configureStore from './stores';
import './main.less';
const store = configureStore();
ReactDOM.render(
    <Provider store={store}>
        <Router history={browserHistory}>
            <Route path="/"  component={App}>
                <IndexRoute component={AllMemosRoute}/>
                <Route path="/todo" component={TodoRoute}/>
                <Route path="/doing" component={DoingRoute}/>
                <Route path="/done" component={DoneRoute}/>
            </Route>
        </Router>
   </Provider>,
 document.body.appendChild(document.createElement('div')))

这里我们从 react-redux 中猎取到 Provider 组件,我们把它衬着到运用的最外层。
他须要一个属性 store ,他把这个 store 放在context里,给Router(connect)用。

4.3 Store

app/store/index.jsx

import { createStore } from 'redux';
import reducer from '../reducers';
export default function configureStore(initialState) {
  const store = createStore(reducer, initialState);
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers');
      store.replaceReducer(nextReducer);
    });
  }
  return store;
}

4.4 Action 建立函数和常量

app/action/index.jsx

'use strict';
/*
 * @author Damonare 2016-12-10
 * @version 1.0.0
 * action 范例
 */
export const Add_Todo = 'Add_Todo';
export const Change_Todo_To_Doing = 'Change_Todo_To_Doing';
export const Change_Doing_To_Done = 'Change_Doing_To_Done';
export const Change_Done_To_Doing = 'Change_Done_To_Doing';
export const Change_Doing_To_Todo = 'Change_Doing_To_Todo';
export const Search='Search';
export const Delete_Todo='Delete_Todo';
/*
 * action 建立函数
 * @method  addTodo增加新事项
 * @param  {String} text 增加事项的内容
 */
export function addTodo(text) {
  return {
      type: Add_Todo,
      text
  }
}
/*
 * @method  search 查找事项
 * @param  {String} text 查找事项的内容
 */
export function search(text) {
  return {
      type: Search,
      text
  }
}
/*
 * @method  changeTodoToDoing 状况由todo转为doing
 * @param  {Number} index 须要转变状况的事项的下标
 */
export function changeTodoToDoing(index) {
  return {
      type: Change_Todo_To_Doing,
      index
  }
}
/*
 * @method  changeDoneToDoing 状况由done转为doing
 * @param  {Number} index 须要转变状况的事项的下标
 */
export function changeDoneToDoing(index) {
  return {
      type: Change_Done_To_Doing,
      index
  }
}
/*
 * @method  changeDoingToTodo 状况由doing转为todo
 * @param  {Number} index 须要转变状况的事项的下标
 */
export function changeDoingToTodo(index) {
  return {
      type: Change_Doing_To_Todo,
      index
  }
}
/*
 * @method  changeDoingToDone 状况由doing转为done
 * @param  {Number} index 须要转变状况的事项的下标
 */
export function changeDoingToDone(index) {
  return {
      type: Change_Doing_To_Done,
      index
  }
}
/*
 * @method  deleteTodo 删除事项
 * @param  {Number} index 须要删除的事项的下标
 */
export function deleteTodo(index) {
  return {
      type: Delete_Todo,
      index
  }
}

在声明每一个返回 action 函数的时刻,我们须要在头部声明这个 action 的 type,以便好构造治理。
每一个函数都邑返回一个 action 对象,所以在 容器组件内里挪用

text =>
  dispatch(addTodo(text))

就是挪用dispatch(action) 。

4.5 Reducers

app/reducers/index.jsx

import { combineReducers } from 'redux';
import todolist from './todos';
// import visibilityFilter from './visibilityFilter';

const reducer = combineReducers({
  todolist
});

export default reducer;

app/reducers/todos.jsx

import {
    Add_Todo,
    Delete_Todo,
    Change_Todo_To_Doing,
    Change_Doing_To_Done,
    Change_Doing_To_Todo,
    Change_Done_To_Doing,
    Search
} from '../actions';
let todos;
(function() {
    if (localStorage.todos) {
        todos = JSON.parse(localStorage.todos)
    } else {
        todos = []
    }
})();
function todolist(state = todos, action) {
    switch (action.type) {
            /*
        *  增加新的事项
        *  并举行当地化存储
        *  运用ES6睁开运算符链接新事项和旧事项
        *  JSON.stringify举行对象深拷贝
        */
        case Add_Todo:
            localStorage.setItem('todos', JSON.stringify([
                ...state, {
                    todo: action.text,
                    istodo: true,
                    doing: false,
                    done: false
                }
            ]));
            return [
                ...state, {
                    todo: action.text,
                    istodo: true,
                    doing: false,
                    done: false
                }
            ];
            /*
            *  将todo转为doing状况,注重action.index的范例转换
            */
        case Change_Todo_To_Doing:
            localStorage.setItem('todos', JSON.stringify([
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: true,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ]));
            return [
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: true,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ];
            /*
            *  将doing转为done状况
            */
        case Change_Doing_To_Done:
            localStorage.setItem('todos', JSON.stringify([
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: false,
                    done: true
                },
                ...state.slice(parseInt(action.index) + 1)
            ]));
            return [
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: false,
                    done: true
                },
                ...state.slice(parseInt(action.index) + 1)
            ];
            /*
            *  将done转为doing状况
            */
        case Change_Done_To_Doing:
            localStorage.setItem('todos', JSON.stringify([
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: true,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ]));
            return [
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: false,
                    doing: true,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ];
            /*
            *  将doing转为todo状况
            */
        case Change_Doing_To_Todo:
            localStorage.setItem('todos', JSON.stringify([
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: true,
                    doing: false,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ]));
            return [
                ...state.slice(0, action.index),
                {
                    todo:state[action.index].todo,
                    istodo: true,
                    doing: false,
                    done: false
                },
                ...state.slice(parseInt(action.index) + 1)
            ];
            /*
            *  删除某个事项
            */
        case Delete_Todo:
            localStorage.setItem('todos', JSON.stringify([
                ...state.slice(0, action.index),
                ...state.slice(parseInt(action.index) + 1)
            ]));
            return [
                ...state.slice(0, action.index),
                ...state.slice(parseInt(action.index) + 1)
            ];
            /*
            *  搜刮
            */
        case Search:
        let text=action.text;
        let reg=eval("/"+text+"/gi");
            return state.filter(item=> item.todo.match(reg));
        default:
            return state;
    }
}
export default todolist;

详细的展示组件这里就不排列代码了,感兴趣的能够戳这:备忘录展示组件地点

跋文

严厉来说,这个备忘录并非运用的react百口桶,毕竟另有一部分less代码,不过这一个运用也算是比较周全的运用了react+react-router+redux,作为react百口桶手艺进修的练手的小项目再合适不过了。假如您对这个小东西感兴趣,迎接戳这:React百口桶完成浅易备忘录给个star。

    原文作者:damonare
    原文地址: https://segmentfault.com/a/1190000007862103
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞