Redux 有三个基本的原则:

  1,单一状态树,redux 只使用一个javascript 对象来保存整个应用的状态。 状态树样式如下:

const state = {
     count: 0
}

  2,状态是只读的,它的意思不是说不能修改state,如果不能修改状态,那页面就完成静态化了,没有什么作用了,它想表达的是,我们不能直接修改state。修改state的唯一办法是发送一个action,让action 来告诉Redux,  页面上有事情发生了,action也很简单,就是一个JavaScript 对象,用来描述了页面上到底发生了什么变化。 格式如下:

{
    type: 'MINUS'
}

  作为action的对象,必须有一个属性type, 用来告知Redux 页面上发生了什么,比如页面有一个按钮,我们点击了一下, 发送了一个上面的action,  Redux 就知道,有人点击了减号按钮,那相应的,状态state就要做一次减法操作,在原来的state基础上减1(这里默认进行减1操作).  所以, 也可以这么说,  action就表示了我们想对state 执行什么样的操作, state 将要做出什么样的变化. 当然,这是最简单的action.  再举一个例子, 如果用户点击减号按钮时,想要减少任意值, 那怎么办? 我们肯定要把用户想要减少的值告诉Redux, 这时就要要求action 携带这个数据, 其实, 也很容易就能办到, 因为action 只是一个普通的JavaScript对象, 想要带什么数据, 直接给它增加属性就可以了. 如下action就表示,我们想要减少5

{
  type: 'MINUS',
  minusValue:  5
}

  我们发送了一个action,表示我们想要更改state, 那要在什么地方去改变我们的state,那就是reducer 应该做的事情了。 reducer 就是一个纯函数,它接受state, 和action 作为参数,然后返回一个新的state. 接受state, action 两个参数,就表示它可以根据action去改变state, 这里一定要注意,它一定是返回一个新的state,不要去改变原state的状态

function counterReducer(state, action) {
    switch (action.type){
        case 'ADD':
            return state + 1;
        default:
            return state;
    }
}

  有了state,action, reducer,它们是怎么串联起来的,那就是store。

  首先,我们整个应用的状态就是存储在store中,由store来维持整个应用的状态。其次它提供了store.dispatch 来发起action, 发起一个action后,提供了subscribe 监听这个action带来的状态的变化,状态发生变化后,它提供了getState() 来获取更新的state. 那怎么创建store, redux 提供了一个createStore方法,它接收reducer作为参数,返回store, 它还可以接受一个可选的state, 作为默认初始值。

  你可以看到redux执行的是单向数据流,发送一个action给store, store 就会把当前的state和action传递给reducer, reducer 会计算出一个新的状态,通过store.getState()获取到最新的状态。

  现在写一个加减项目来体验一下redux. 页面中有三个按钮,一个加,一个减和一个重置,还有一个h1 显示结果。在文件夹中新建一个index.html,一个counter.js 和一个counter.css文件

  整个html代码如下:head 中引入css 和redux,  body中就是三个按钮,和一个h1, 底部引入js文件

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Redux</title>
  <link rel="stylesheet" href="./counter.css">
  <script src="https://cdn.bootcss.com/redux/3.7.2/redux.js"></script>
</head>
<body>
  <div class="container">
    <h1 id="counter">0</h1>
    <button class="btn btn-blue" id="add">Add</button>
    <button class="btn btn-green" id="minus">Minus</button>
    <button class="btn btn-red" id="reset">Reset</button>
  </div>
  <script src="./counter.js"></script>
</body>
</html>

  counter.css 文件如下:

body {
    padding: 40px;
    font-family: "helvetica neue", sans-serif;
  }

  .container {
    width: 600px;
    margin: auto;
    color: black;
    padding: 20px;
    text-align: center;
  }
  .container h1 {
    margin:;
    padding: 20px;
    font-size: 48px;
  }
  .container .btn {
    border:;
    padding: 15px;
    margin: 10px;
    width: 20%;
    font-size: 15px;
    outline: none;
    border-radius: 3px;
    color: #FFF;
    font-weight: bold;
  }

  .btn.btn-blue {
    background-color: #55acee;
    box-shadow: 0px 5px 0px 0px #3C93D5;
  }

  .btn.btn-blue:hover {
     background-color: #6FC6FF;
  }

  .btn.btn-green {
    background-color: #2ecc71;
    box-shadow: 0px 5px 0px 0px #15B358;
  }

  .btn.btn-green:hover {
    background-color: #48E68B;
  }

  .btn.btn-red {
    background-color: #d84a4a;
    box-shadow: 0px 5px 0px 0px #a73333;
  }

  .btn.btn-red:hover {
    background-color: #f15656;
  }

  在页面中的效果如下:

  现在我们来写counter.js 文件,就是如何使Redux

  1, 首先我们要创建store, 那创建store之前 就要先定义state, reducer. 我们的state 非常简单, 就是一个count, 来展示数字

const state = {count: 0};

  2, 创建reducer. 在这个简单的小例子中,我们有三个action, 当点击Add按钮时,它会发送ADD, 相应的count就会加1, 当点击Minus按钮时,它会发送MINUS, 相应的count 就会减1. 当点击Reset, 它会发送RESET, 相应的count置为0;  所以在reducer中我们要处理3个action,  相应的返回3个state,

function counterReducer(state, action) {
     // reducer 都会返加一个全新的state, 所以在这里声明了一个新的变量nextState,代表一个新的状态,用于返回
    var nextState = {
        count: state.count
    }
    switch (action.type){
        case 'ADD':         // ADD action, count 加1,
            nextState.count = state.count + 1;
            return nextState;
        case 'MINUS':       // MINUS action, count 减1
            nextState.count = state.count - 1;
            return nextState;
        case 'RESET':       // RESET action, count 置为0
            nextState.count = 0;
            return nextState;
        default:            // 当发送一个未知action时的后退处理
            return state;
    }
}

  在设置reducer时,一定要注意:

    1, 它是一个纯函数,返回一个新的状态;

    2,  在switch case 语句中,一定要提供default, 返回当前状态

  3, 现在有了state 和 reducer,可以创建store 了,Redux 提供了一个createStore方法来创建store, 它拉受一个必要的参数reducer, 后面可以跟可选的参数state

const store = Redux.createStore(counterReducer, state);  // 创建store

  4, 我们要在点击按钮时更改状态,那就要发送action

// Add 按钮发送ADD action
document.getElementById('add').addEventListener('click', function(){
    store.dispatch({type: 'ADD'})
})
// Minus按钮发送MINUS action
document.getElementById('minus').addEventListener('click', function() {
    store.dispatch({type: 'MINUS'})
})
// Reset按钮发送RESET action
document.getElementById('reset').addEventListener('click', function() {
    store.dispatch({type: 'RESET'})
})

  5, 发送action 之后, state就会发生变化,我们页面中怎么获取到最新的状态进行展示?那就要订阅这个action事件,以便state 发生变化后,我们能及时更新state, 用到store.subscribe 函数, 它接受一个函数,只要有action发生,状态改变,它接受的回调函数就会触发。 我们可以在store.subscribe接受的函数中,获取store中的最新状态,获取最新状态,是用store.getState() 方法。 首先写一个render方法,获取最新的state, 然后给页面中的h1赋值。然后把render 方法传递给store.subscribe

const counterEl = document.getElementById('counter');

// render 函数获取状态,并给页面赋值
function render() {
    var state =  store.getState();  // 获取状态
    counterEl.innerHTML = state.count.toString();
}

// 订阅action, 以便action触发state, 状态发生变化后我们能及时得到通知,获取最新的state.
store.subscribe(render);

  6, 现在点击Add 按钮,就可以看到页面中在不停的加1, 点击Minus,不停地减1,而点击Reset, 页面上会显示0。

  基本功能是实现了,我们再来讨论一下react的最佳实践。

  1, 最好不要给createStore 传递初始state参数, 我们可以在reducer中赋值初始状态。 在整个redux应用中,默认的初始state是undefined, 因此,在reducer中, 我们可以判断state是不是undefined, 如是,我们赋初值。在reducer的开头位置写如下内容

if (typeof state === 'undefined') {
    return {count : 0};
}

  2, 初始调用一下render函数,获取到state的初始值,渲染到页面中,那么在页面中的h1中,我们就不用写0; 在store.subscribe(render) 语句前,调用一下render函数。

  整个counter.js 如下

// reducer
function counterReducer(state, action) {
        if (typeof state === 'undefined') {
            return {count : 0};
        }
    var nextState = {       // 新状态
        count: state.count
    }
    switch (action.type){
        case 'ADD':         // ADD action, count 加1,
            nextState.count = state.count + 1;
            return nextState;
        case 'MINUS':       // MINUS action, count 减1
            nextState.count = state.count - 1;
            return nextState;
        case 'RESET':       // RESET action, count 置为0
            nextState.count = 0;
            return nextState;
        default:            // 当发送一个未知action时的后退处理
            return state;
    }
}

const store = Redux.createStore(counterReducer);    // store
const counterEl = document.getElementById('counter');

function render() {
    var state =  store.getState();  // 获取状态
    counterEl.innerHTML = state.count.toString();
}
render();            // 先调用一次render函数,把初始值放到页面中;
store.subscribe(render);

document.getElementById('add').addEventListener('click', function(){
    store.dispatch({type: 'ADD'})
})

document.getElementById('minus').addEventListener('click', function() {
    store.dispatch({type: 'MINUS'})
})

document.getElementById('reset').addEventListener('click', function() {
    store.dispatch({type: 'RESET'})
})

  现在我们页面中只有一个加减功能, 逻辑比较简单,一个reducer可以解决问题, 如果页面中有很多功能,一个reducer就要处理很多action, 逻辑太复杂,代码量也会非常庞大,不利于代码的维护,所以肯定不能把所有的action 的处理放到一个reducer中,最好按逻辑进行reducer 划分。现在再在页面中添加一个功能,显示我们在input中输入的内容,就和to do list 一样。html页面中增加一个input 和三个按钮

<body>
<!--    加减按钮-->
  <div class="container">
    <h1 id="counter">0</h1>
    <button class="btn btn-blue" id="add">Add</button>
    <button class="btn btn-green" id="minus">Minus</button>
    <button class="btn btn-red" id="reset">Reset</button>
  </div>
    <!-- to do list按钮-->
  <div class="container">
    <ul id="todoList"></ul>
    <div>
          <div>
              <input type="text" placeholder="请输入任务内容" id="todo">
          </div>
        <button class="btn btn-green" id="new">New</button>
        <button class="btn btn-blue" id="delete">Delete</button>
        <button class="btn btn-red" id="delete_all">Delete All</button>
    </div>
  </div>
  <script src="./counter.js"></script>
</body>

  action非常简单,点击new按钮的时候,它会发送NEW action 事件,同时带着我们在输入框中输入的内容,点击delete的时候,它会发送 DELETE action,  那就删除一条任务,

当点击Delete All的时候,清空任务。

var todoInput = document.getElementById('todo');  // 获取input输入框

// to do list 功能的action
document.getElementById('new').addEventListener('click',() => {
    store.dispatch({type:'NEW', payload: todoInput.value});  // NEW action要带着input输入框中的内容
})
document.getElementById('delete').addEventListener('click',() => {
    store.dispatch({type:'DELETE' });
})
document.getElementById('delete_all').addEventListener('click',() => {
    store.dispatch({type:'DELETE_ALL'});
})

  对于三个action, 我们要写一个reducer:

// to do list reducer
function todoReducer(state, action) {
    if (typeof state === 'undefined') {
        return {todos: []}
    }
    var nextState = Object.assign({}, state);  // 创建一个新的变量
    switch (action.type) {
        case 'NEW' :
            nextState.todos.push(action.payload);
            return nextState;
        case 'DELETE':
            nextState.todos.pop();
            return nextState
        case 'DELETE_ALL':
            nextState.todos = [];
            return nextState;
        default:
            return state;
    }
}

  现在遇到一个问题,我们的代码中有两个reducer, 但是createStore 函数却只能接受一个reducer,所以我们要把两个reducer合并成 一个reducer, 成为整个项目的根reducer,

正好Redux提供了一个combineReducer 方法,可以把多个reducer 合并成一个reducer. combineReducer 接受一个对象,对象的键,我们可以取,键对应的值就是我们定义的reducer, 但是我们一般都会取与reducer相同的键,方便记忆。

const store = Redux.createStore(Redux.combineReducers({
    counterReducer: counterReducer,
    todoReducer:todoReducer
}));  

  当把多个reducer合并到一起的时候,store中的state也发生了变化,你可以console.log(store.getState()) 看一下,你会发现,state这个对象多了几个属性,属性名正好就是我们定义的combineReducer中的接受的对象中的键,属性值则是各个对应的reducer中的定义的状态,在我们这个例子中,state

{  counterReducer: {    count: 0  },  todoReducer: {    todos:[]  }}

   在combineReducer接受的对象中,counterReducer作为属性名,对应的属性值,也就是对应的reducer是counterReducer, 所以在state中,属性名counterReducer对应的属性值就是counterReducer中对应的状态 {counter: 0 }, todoReducer也是这个原理, 在chrome的控制台中效果如下:

  现在再定义一个函数,渲染获取到的状态到页面中。

var todoList = document.getElementById('todoList');

function renderList(state) {
    todoList.innerHTML = '';
    for (var index = 0; index < state.todoReducer.todos.length; index++) {
        var element = state.todoReducer.todos[index];
        var li = document.createElement('li');
        var todo = state.todoReducer.todos[index];
        li.innerHTML = todo.toString();
        todoList.appendChild(li);
    }
}

  最后把这个函数放到render中,

function render() {
    var state =  store.getState();  // 获取状态
    counterEl.innerHTML = state.counterReducer.count.toString();
    renderList(state);
}

  整个couter.js 如下:

// 处理加减counter 的 reducer
function counterReducer(state = {count:0}, action) {
    var nextState = {
        count: state.count
    }
    switch (action.type){
        case 'ADD':
            nextState.count = state.count + 1;
            return nextState;
        case 'MINUS':
            nextState.count = state.count - 1;
            return nextState;
        case 'RESET':
            nextState.count = 0;
            return nextState;
        default:
            return state;
    }
}
// 处理to do list中的reducer
function todoReducer(state, action) {
    if (typeof state === 'undefined') {
        return {todos: []}
    }
    var nextState = Object.assign({}, state);  // 创建一个新的变量
    switch (action.type) {
        case 'NEW' :
            nextState.todos.push(action.payload);
            return nextState;
        case 'DELETE':
            nextState.todos.pop();
            return nextState
        case 'DELETE_ALL':
            nextState.todos = [];
            return nextState;
        default:
            return state;
    }
}

// 利用Redux.combinerReducer, 把多个reducer合并到一个reducer中,然后传递给createStore函数,创建store
const store = Redux.createStore(Redux.combineReducers({
    counterReducer: counterReducer,
    todoReducer:todoReducer
}));  

// 获取到页面中counter和todolist显示元素,就是 h1, ul, 以便我们在render函数中获取状态给它赋值,从而显示到页面
const counterEl = document.getElementById('counter');
const todoList = document.getElementById('todoList');

// 渲染to do list函数, 注意这里的状态的取值,我们要取state中的todoReducer中的状态
function renderList(state) {
    todoList.innerHTML = '';
    for (var index = 0; index < state.todoReducer.todos.length; index++) {
        var element = state.todoReducer.todos[index];
        var li = document.createElement('li');
        var todo = state.todoReducer.todos[index];
        li.innerHTML = todo.toString();
        todoList.appendChild(li);
    }
}
//整个应用的render函数,就是把处理各个状态的render渲染函数组合中一起
function render() {
    var state =  store.getState();
    counterEl.innerHTML = state.counterReducer.count.toString(); //处理counter的状态
    renderList(state) // 处理todo list的渲染函数。
}

render();
store.subscribe(render);

// 点击加减1的action
document.getElementById('add').addEventListener('click', function(){
    store.dispatch({type: 'ADD'})
})
document.getElementById('minus').addEventListener('click', function() {
    store.dispatch({type: 'MINUS'})
})
document.getElementById('reset').addEventListener('click', function() {
    store.dispatch({type: 'RESET'})
})

// to do list 功能的action

var todoInput = document.getElementById('todo');
document.getElementById('new').addEventListener('click',() => {
    store.dispatch({type:'NEW', payload: todoInput.value});
})
document.getElementById('delete').addEventListener('click',() => {
    store.dispatch({type:'DELETE' });
})
document.getElementById('delete_all').addEventListener('click',() => {
    store.dispatch({type:'DELETE_ALL'});
})

  为了更深入地学习Redux , 我们用ES6 重构一下我们的例子,简单演示,我只使用counterReducer , 来处理加减功能。新建一个文件夹,然后npm init -y 创建package.json 文件, 再  npm i babel-core babel-loader  babel-preset-es2015 babel-preset-stage-3 webpack webpack-dev-server --save-dev  安装webpack, babel 开发依赖。创建一个webpack.config.js 用于打包,.babelrc文件使用babel

webpack.config.js 文件如下

const path = require('path');

module.exports = {
    entry: path.join(__dirname, 'index.js'),
    output: {
        path: path.join(__dirname),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
}

  .babelrc 文件如下:

{"presets" : ["es2015", "stage-3"]}

  现在再新建一个index.html文件,index.html如下,可以去掉redux 引入, script标签也应该引入bundle.js打包后的文件。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Redux</title>
  <link rel="stylesheet" href="./counter.css">
</head>
<body>
  <div class="container">
    <h1 id="counter"></h1>
    <button class="btn btn-blue" id="add">Add</button>
    <button class="btn btn-green" id="minus">Minus</button>
    <button class="btn btn-red" id="reset">Reset</button>
  </div>
  <script src="./bundle.js"></script>
</body>
</html>

  新建一个index.js 文件,counter.js文件只处理了加减部分,此时要通过import引入redux, 当然我们首先要npm install redux -S 来安装它

import {createStore} from 'redux';  // 引入createStore 来创建 store, import Redux from 'redux' 是不对的

// reducer
function counterReducer(state, action) {
        if (typeof state === 'undefined') {
            return {count : 0};
        }
    var nextState = {       // 新状态
        count: state.count
    }
    switch (action.type){
        case 'ADD':         // ADD action, count 加1,
            nextState.count = state.count + 1;
            return nextState;
        case 'MINUS':       // MINUS action, count 减1
            nextState.count = state.count - 1;
            return nextState;
        case 'RESET':       // RESET action, count 置为0
            nextState.count = 0;
            return nextState;
        default:            // 当发送一个未知action时的后退处理
            return state;
    }
}

const store = createStore(counterReducer);    // store
const counterEl = document.getElementById('counter');

function render() {
    var state =  store.getState();  // 获取状态
    counterEl.innerHTML = state.count.toString();
}
render();            // 先调用一次render函数,把初始值放到页面中;
store.subscribe(render);

document.getElementById('add').addEventListener('click', function(){
    store.dispatch({type: 'ADD'})
})

document.getElementById('minus').addEventListener('click', function() {
    store.dispatch({type: 'MINUS'})
})

document.getElementById('reset').addEventListener('click', function() {
    store.dispatch({type: 'RESET'})
})

  现在启动npm , 在package.json文件中scripts字段中输入  "dev": "webpack-dev-server",  在当前文件夹中调用 命令窗口, 输入npm run dev,就可以启动项目。在浏览器中http://localhost:8080/ , 功能没有问题。

Redux 学习(1) ----- Redux介绍的更多相关文章

  1. redux学习

    redux学习: 1.应用只有一个store,用于保存整个应用的所有的状态数据信息,即state,一个state对应一个页面的所需信息 注意:他只负责保存state,接收action, 从store. ...

  2. React Redux学习笔记

    React Router React Router 使用教程 Redux中间件middleware [译]深入浅出Redux中间件 Redux学习之一:何为middleware? ES6 ES6新特性 ...

  3. React+Redux学习笔记:React+Redux简易开发步骤

    前言 React+Redux 分为两部分: UI组件:即React组件,也叫用户自定义UI组件,用于渲染DOM 容器组件:即Redux逻辑,处理数据和业务逻辑,支持所有Redux API,参考之前的文 ...

  4. Redux学习之我对于其工作流程的理解和实践

      目录 1 工作流程图 2 各部位职责 3 Demo   1 工作流程图   2 各部位职责 我在理解这个流程图的时候,采用的是一种容易记住的办法,并且贴切实际工作职责. 我们可以把整个Redux工 ...

  5. redux学习总结

    redux学习总结 *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !imp ...

  6. Redux学习及应用

    Redux学习及应用 一:Redux的来源? Redux 是 JavaScript 状态容器,提供可预测化的状态管理.Redux是由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂 ...

  7. 人工智能深度学习Caffe框架介绍,优秀的深度学习架构

    人工智能深度学习Caffe框架介绍,优秀的深度学习架构 在深度学习领域,Caffe框架是人们无法绕过的一座山.这不仅是因为它无论在结构.性能上,还是在代码质量上,都称得上一款十分出色的开源框架.更重要 ...

  8. iOS学习之NSBundle介绍和使用

    iOS学习之NSBundle介绍和使用 http://blog.csdn.net/totogo2010/article/details/7672271 新建一个Single View Applicat ...

  9. ASP.NET Core Web开发学习笔记-1介绍篇

    ASP.NET Core Web开发学习笔记-1介绍篇 给大家说声报歉,从2012年个人情感破裂的那一天,本人的51CTO,CnBlogs,Csdn,QQ,Weboo就再也没有更新过.踏实的生活(曾辞 ...

随机推荐

  1. 20175330 2018-2019-2 《Java程序设计》第八周学习总结

    # **教材学习内容总结### 本周学习<Java程序设计>第十五章:*** 泛型: 泛型(Generics)的主要目的是可以建立具有类型安全的集合框架,如链表.散列映射等数据结构.泛型类 ...

  2. AI R-CNN目标检测算法

    Region-CNN,简称R-CNN,是首次将深度学习应用于目标检测的算法. bounding box IOU 非极大值抑制 selective search 参考链接: https://blog.c ...

  3. linux安装jdk1.8(rpm方式)

    在Oracle官网下载64位的jdk1.8版本 jdk1.8: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloa ...

  4. jdk1.8之线程中断

    在Core Java中有这样一句话:"没有任何语言方面的需求要求一个被中断的程序应该终止.中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 " 线程中断不会使线 ...

  5. python第四章:函数--小白博客

    Python函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可 ...

  6. Python-递归复习-斐波那契-阶乘-52

    # 超过最大递归限制的报错# 只要写递归函数,必须要有结束条件. # 返回值# 不要只看到return就认为已经返回了.要看返回操作是在递归到第几层的时候发生的,然后返回给了谁.# 如果不是返回给最外 ...

  7. Python_每日习题_0002_个税计算

    # 题目 企业发放的奖金根据利润提成.利润(I)低于或等于10万元时, # 奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10%提成, # 高于10万元的部分,可提成7.%:2 ...

  8. 关于iframe页面里的重定向问题

    最近公司做的一个功能,使用了iframe,父页面内嵌子页面,里面的坑还挺多的,上次其实就遇到过,只不过今天在此描述一下. 请允许我画个草图: 外层大圈是父级页面,里层是子级页面,我们是在父级引用子级页 ...

  9. fun = [lambda x: x*i for i in range(4)] 本质解析/原理,LEGB规则 闭包原理

    命名空间,闭包原理,参考点击本文 一.问题描述 fun = [lambda x: x*i for i in range(4)] for item in fun: print(item(1)) 上述式子 ...

  10. semantic-ui 图片

    1.基础样式 方式一:因为图片是使用img标签,所以直接将class加载img标签中即可.不过要注意的是,class中要指定是ui image. 方式二:使用一个span或者div将img标签包裹,然 ...