前端笔记之React(五)Redux深入浅出
一、Redux整体感知
Redux是JavaScript状态管理容器,提供了可被预测状态的状态管理容器。来自于Flux思想,Facebook基于Flux思想,在2015年推出Redux库。
官方git:https://github.com/reduxjs/redux
首先要引redux.js包,这个包提供了Redux对象,这个对象可以调用Redux.createStore()方法。
- <body>
- <h1 id="info"></h1>
- <button id="btn1">加</button>
- <button id="btn2">减</button>
- <button id="btn3">乘</button>
- <button id="btn4">除</button>
- <script type="text/javascript" src="redux.min.js"></script>
- <script type="text/javascript">
- //这是一个 reducer,形式为 (state, action) => state 的纯函数。
- //纯函数:函数内部,不改变传入的参数,只return新值。
- //描述了 action 如何把 state 转变成下一个 state。
- const reducer = (state = {"v" : 10} , action)=>{
- if(action.type == "ADD"){
- return {"v" : state.v + 1};
- }else if(action.type == "MINUS"){
- return {"v" : state.v - 1};
- }else if(action.type == "CHENG2"){
- return {"v" : state.v * 2}
- }
- return state;
- }
- //创建 Redux store 来存放应用的状态。
- //store翻译为“仓库”,这是一个存放数据并且可以操作数据的东西
- const store = Redux.createStore(reducer);
- //创建一个视图函数,并且将store显示到视图上。
- const render = ()=>{
- //store的getState()方法可以得到仓库中的数据
- document.getElementById("info").innerHTML = store.getState().v;
- }
- render(); //调用render函数
- //要将store注册到视图,这样的话,当store中的数据变化候,就能自动调用render函数
- store.subscribe(render);
- // 应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。
- // 惟一改变state的办法是触发action,一个描述发生什么的对象。
- // 为了描述 action 如何改变 state 树,你需要编写 reducers。
- document.getElementById("btn1").onclick = function(){
- // 改变内部 state 惟一方法是 dispatch 一个 action,type表示要做的动作
- store.dispatch({"type":"ADD"})
- }
- document.getElementById("btn2").onclick = function(){
- store.dispatch({"type":"MINUS"})
- }
- document.getElementById("btn3").onclick = function(){
- store.dispatch({"type":"CHENG"})
- }
- </script>
- </body>
创建一个叫做reducer的函数,reducer中“维持”一个量,叫state,初始值是{"v" : 0}。
这个函数你必须知道两点:
1)它是一个纯函数,在函数内部不改变{"v" : 0}的,不改变state对象的,只是返回了新的state。
- const reducer = (state = {"v" : 0} , action)=>{
- if(action.type == "ADD"){
- return {"v" : state.v + 1};
- }else if(action.type == "MINUS"){
- return {"v" : state.v - 1};
- }
- return state;
- }
2)这个函数提供了可被预测的功能。
reducer中的state,就像被关进了保险箱。任何对这个state的变化,只能是通过dispatch一个action来改变的。换句话说,只有dispatch一个action才能改变这个保险箱中的数据。
在reducer函数中,用if语句来表示对state可能发生变化的罗列,只有罗列在楼层里面的改变,才会发生:
- if(action.type==""){
- return 新的state
- }else if(action.type ==""){
- return 新的state
- }else if(action.type ==""){
- return 新的state
- }
示例代码
创建store仓库,这个仓库创建时需要提供reducer,所以可以认为store就是reducer,reducer就是store。
- const store = Redux.createStore(reducer);
reducer是一个纯函数,而store提供了三个方法:
store.subscribe() 注册到视图
store.getState() 得到数据
store.dispatch() 发送action
学习redux和react结合,到时候就不用注册到视图。
然后创建监听:
- document.getElementById("btn1").onclick = function(){
- store.dispatch({"type":"ADD"})
- }
- document.getElementById("btn2").onclick = function(){
- store.dispatch({"type":"MINUS"})
- }
- document.getElementById("btn3").onclick = function(){
- store.dispatch({"type":"CHENG"})
- }
示例代码
点击按钮,store要dispatch出一个action。所谓的action就是一个JSON,这个JSON必须有type属性,值为大写字母。这个action没有任何意义,比如{"type":"ADD"},但reducer认识这个action,可以产生变化!
案例2,添加载荷:
- <body>
- <h1 id="info"></h1>
- <button id="btn1">加</button>
- <input type="text" id="txt">
- <button id="btn2">加输入的</button>
- <script type="text/javascript" src="redux.min.js"></script>
- <script type="text/javascript">
- const reducer = (state = {"v" : 10}, {type, data})=>{
- if(type == "ADD"){
- return {"v" : state.v + action.data}
- return {"v" : state.v + data}
- }
- return state;
- }
- const store = Redux.createStore(reducer);
- const render = ()=>{
- document.getElementById("info").innerHTML = store.getState().v;
- }
- render();
- store.subscribe(render);
- document.getElementById("btn1").onclick = function(){
- store.dispatch({ type: 'ADD', data:100});
- }
- document.getElementById("btn2").onclick = function(){
- var a = parseInt(document.getElementById("txt").value);
- //载荷payload。
- store.dispatch({"type":"ADD" , data:a});
- }
- </script>
- </body>
唯一可以改变state的方式dispatch一个action
案例3:添加数组
- <body>
- <div id="box">
- <p><input type="text" id="name"></p>
- <p><input type="text" id="age"></p>
- <button id="btn">增加</button>
- <ul id="List">
- </ul>
- </div>
- <script type="text/javascript" src="redux.min.js"></script>
- <script type="text/javascript">
- //初始数据
- const initObj = {
- "arr" : [
- {"name" : "小明" , "age" : 12},
- {"name" : "小红" , "age" : 13},
- {"name" : "小强" , "age" : 14}
- ]
- };
- //reducer函数
- const reducer = (state = initObj, {type , name , age}) => {
- if(type == "ADD_STUDENT"){
- //不能push改变传入的原数组,所以要返回新数组
- return {
- "arr" : [
- {name , age} ,
- ...state.arr
- ]
- }
- }
- return state;
- }
- //创建store
- const store = Redux.createStore(reducer);
- //视图函数
- const render = function(){
- //清空ul
- document.getElementById("List").innerHTML = "";
- //创建li
- for(var i = 0 ; i < store.getState().arr.length ; i++){
- var li = document.createElement("li");
- var storeArr = store.getState().arr[i]
- li.innerHTML = storeArr.name + storeArr.age+"岁"
- document.getElementById("List").appendChild(li);
- }
- }
- render();//运行视图函数
- store.subscribe(render);//注册到视图
- document.getElementById("btn").onclick = function(){
- var name = document.getElementById("name").value;
- var age = document.getElementById("age").value;
- //发出Action,这是唯一能够改变store的途径
- store.dispatch({"type" : "ADD_STUDENT", name, age })
- }
- </script>
- </body>
案例4,加深练习:
- <body>
- <h1 id="info"></h1>
- <h1 id="info2"></h1>
- <button id="btn">+</button>
- <script type="text/javascript" src="redux.min.js"></script>
- <script type="text/javascript">
- var initObj = {
- "a" : {
- "b" : {
- "c" : {
- "v" : 10
- }
- },
- "m" : 8
- }
- }
- const reducer = (state = initObj , action) => {
- if(action.type == "ADD"){
- return {
- ...state,
- "a" : {
- ...state.a ,
- "b" : {
- ...state.a.b,
- "c" : {
- ...state.a.b.c ,
- "v" : state.a.b.c.v + 1
- }
- }
- }
- }
- }
- return state;
- }
- const store = Redux.createStore(reducer);
- const render = ()=>{
- document.getElementById("info").innerHTML = store.getState().a.b.c.v;
- document.getElementById("info2").innerHTML = store.getState().a.m;
- }
- render();
- store.subscribe(render);
- document.getElementById("btn").onclick = function(){
- store.dispatch({"type" : "ADD"});
- }
- </script>
- </body>
二、Redux和React进行结合开发
在React开发的时候使用Redux可预测状态容器,要装两个新的依赖:
redux:提供createStore、combineReducers、bindActionCreators等功能
react-redux:只提供两个东西,<Provider>组件、connect()函数。
安装两个依赖:
- npm install --save redux react-redux
创建reducers文件夹,创建index.js,这个文件暴露一个纯函数:
reducers/index.js:
- export default (state = { v : 10}, action) => {
- return state;
- }
main.js入口文件:
- import React from "react";
- import ReactDOM from "react-dom";
- import {createStore} from "redux";
- import App from "./App.js";
- import reducer from "./reducers"; //reducer函数
- //创建 Redux store 来存放应用的状态。
- const store = createStore(reducer);
- ReactDOM.render(
- <App></App>,
- document.getElementById("app")
- );
有两个问题:
如何让组件能够访问到store中的数据?
如果让store中的数据改变的时候,能够自动更新组件的视图
react-redux解决了这两个问题。
在main.js中创建<Provider>组件,它提供的是一个顶层容器的作用,实现store的上下文传递,是让store能够“注入”到所有组件
react-redux提供Provider组件,可以让容器组件拿到state
Provider在根组件外面包一层,这样一来,App所有的子组件就默认都拿到了state了。
原理是React组件的context属性,就是将store这个对象放到上下文(context)中
- import React from "react";
- import ReactDOM from "react-dom";
- import {createStore} from "redux";
- import {Provider} from "react-redux";
- import App from "./App.js";
- import reducer from "./reducers";
- //创建store仓库
- const store = createStore(reducer);
- ReactDOM.render(
- <Provider store={store}>
- <App></App>
- </Provider>,
- document.getElementById('app')
- );
组件中要获得全局store的值,需要用connect函数,connect提供连接React组件与Redux store的作用。
- connect([mapStateToProps], [mapDispatchToProps])
connect()的第一个参数:mapStateToProps这个函数的第一个参数就是Redux的store,会自动将store的数据作为props绑定到组件上。
connect()的第二个参数:mapDispatchToProps它的功能是,将action作为props绑定到组件上
通俗理解,使用connect可以把state和dispatch绑定到React组件,使得组件可以访问到redux的数据。
常看到下面这种写法:
- export default connect()(App)
App.js
- import React from "react";
- import {connect} from "react-redux";
- class App extends React.Component {
- constructor() {
- super();
- }
- render(){
- return <div>
- <h1>{this.props.v}</h1>
- </div>
- }
- }
- export default connect(
- //这个函数return的对象的值,将自动成为组件的props。
- (state) => {
- return {
- v : state.v
- }
- }
- )(App);
一旦connect()(App); 连接某个组件,此时这个组件就会:当全局store发送改变时,如同自己的props发生改变一样,从而进行视图更新。
在App.js写两个按钮,可以加1,减1:
- import React from "react";
- import {connect} from "react-redux";
- class App extends React.Component {
- constructor() {
- super();
- }
- render(){
- return <div>
- <h1>{this.props.v}</h1>
- <button onClick={()=>{this.props.add()}}>+</button>
- <button onClick={()=>{this.props.minus()}}>-</button>
- </div>
- }
- }
- export default connect(
- (state) => {
- return {
- v : state.v
- }
- },
- (dispatch) => {
- return {
- add(){
- dispatch({"type" : "ADD"});
- },
- minus(){
- dispatch({"type" : "MINUS"});
- }
- }
- }
- )(App);
reducers/index.js提供可预测状态
- export default (state = {"v" : 10} , action) => {
- if(action.type == "ADD"){
- return { "v" : state.v + 1 };
- }else if(action.type == "MINUS"){
- return { "v" : state.v - 1 };
- }
- return state;
- }
学生管理系统小案例:
reducers/index.js
- const initObj = {
- "arr" : [
- {"id" : 1, "name" : "小明" , "age" : 12},
- {"id" : 2, "name" : "小红" , "age" : 13},
- {"id" : 3, "name" : "小刚" , "age" : 14}
- ]
- }
- export default (state = initObj, {type, age, name, id})=>{
- if(type == "ADD_STUDENT"){
- return {
- ...state ,
- "arr" : [
- ...state.arr ,
- {
- "id" : state.arr.reduce((a,b)=>{
- return b.id > a ? b.id : a;
- },0) + 1,
- name,
- age
- }
- ]
- }
- }else if(type == "DEL_STUDENT"){
- return {
- ...state,
- "arr" : state.arr.filter(item=>item.id != id)
- }
- }
- return state;
- }
App.js
- import React from "react";
- import {connect} from "react-redux";
- class App extends React.Component {
- constructor() {
- super();
- }
- //增加学生
- add(){
- var name = this.refs.name.value;
- var age = this.refs.age.value;
- this.props.addStudent(name, age)
- }
- render(){
- return <div>
- <p><input type="text" ref="name"/></p>
- <p><input type="text" ref="age"/></p>
- <button onClick={()=>{this.add()}}>增加学生</button>
- {
- this.props.arr.map((item,index)=>{
- return <p key={item.id}>
- {item.id} {item.name} {item.age}岁
- <button onClick={()=>{this.props.delStudent(item.id)}}>删除</button>
- </p>
- })
- }
- </div>
- }
- }
- export default connect(
- //(state) => {
- // return { arr : state.arr }
- //},
- //简化写法
- ({arr})=>{
- return { arr }
- },
- (dispatch)=>{
- return {
- addStudent(name, age){
- dispatch({"type" : "ADD_STUDENT", name, age})
- },
- delStudent(id){
- dispatch({"type" : "DEL_STUDENT" , id})
- }
- }
- }
- )(App);
原理解析
首先connect之所以会成功,是因为Provider组件:
在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
接收Redux的store作为props,通过context对象传递给子孙组件上的connect
connect做了些什么?
它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收Provider提供的 store 里面的state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
总结:
connect()(App),第一个()中接受两个参数,分别是:mapStateToProps、mapDispatchToProps。
这两个参数都是函数,第一个参数函数return的对象的键名将自动和props进行绑定,第二个参数函数return的对象的键名,也将和props进行绑定。
第一个参数return的对象,是从state中获得值
第二个参数return的对象,是要改变state的值
如果有兴趣,可以看一下connect函数的API文档:
不管应用程序有多大,store只有一个,它就像天神一样“照耀”所有组件,但默认情况下所有组件是不能得到store的数据的,哪个组件要拿数据,就要connect一下,另外App最大组件确实包裹着所有组件,但不代表App组件连接了就代表其他组件也连接了。
三、Redux编程-TodoList
在reducers/index.js中根据项目情况创建reducer:
- const initObj = {
- "todos": [
- {"id" : 1, "title" : "吃饭", "done" : false},
- {"id" : 2, "title" : "睡觉", "done" : false},
- {"id" : 3, "title" : "打豆豆","done" : false}
- ]
- }
- export default (state = initObj, action) => {
- return state;
- }
分别创建TodoHd.js、TodoBd.js、TodoFt.js三个组件:
- import React from "react";
- export default class TodoHd extends React.Component {
- constructor() {
- super();
- }
- render() {
- return <div>
- <h1>我是TodoHd组件</h1>
- </div>
- }
- }
示例代码
App.js引入组件
- import React from "react";
- import {connect} from "react-redux";
- import TodoHd from "./components/TodoHd.js";
- import TodoBd from "./components/TodoBd.js";
- import TodoFt from "./components/TodoFt.js";
- class App extends React.Component {
- constructor() {
- super();
- }
- render() {
- return <div>
- <TodoHd></TodoHd>
- <TodoBd></TodoBd>
- <TodoFt></TodoFt>
- </div>
- }
- }
- export default connect()(App)
- import React from 'react';
- import {connect} from "react-redux";
- import TodoItem from "./TodoItem.js";
- class TodoBd extends React.Component {
- constructor(props){
- super(props);
- }
- render() {
- return (
- <div>
- //{JSON.stringify(this.props.todos)}
- {
- this.props.todos.map(item=>{
- //return <div key={item.id}>{item.title}</div>
- return <TodoItem key={item.id} item={item}></TodoItem>
- })
- }
- </div>
- );
- }
- }
- //connect目的是问“天”要数据,要通天。
- //“通天”是比喻,就是说要问store要数据
- export default connect(
- (state)=>{
- return {
- todos : state.todos
- }
- }
- )(TodoBd);
TodoItem.js
- import React from 'react';
- export default class TodoItem extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div className="todoItem">
- <input type="checkbox" checked={this.props.item.done}/>
- <span>{this.props.item.title}</span>
- <button>删除</button>
- </div>
- );
- }
- }
TodoHd.js增加待办事项
- import React from 'react';
- import {connect} from "react-redux";
- class TodoHd extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <input type="text" ref="titleTxt"/>
- <button onClick={()=>{
- this.props.addTodo(this.refs.titleTxt.value);
- this.refs.titleTxt.value = "";
- }}>添加</button>
- </div>
- );
- }
- }
- //这个组件要通天的目的不是要数据,而是改变数据
- export default connect(
- null ,
- (dispatch)=>{
- return {
- addTodo(title){
- dispatch({"type":"ADDTODO", title})
- }
- }
- }
- )(TodoHd);
reducers/index.js写可预测状态
- const initObj = {
- "todos" : [
- ...
- ],
- }
- export default (state = initObj, action)=>{
- if(action.type == "ADDTODO"){
- return {
- ...state,
- "todos" : [
- ...state.todos,
- {
- "id" : state.todos.reduce((a,b)=>{
- return b.id > a ? b.id : a;
- },0) + 1,
- "title" : action.title,
- "done" : false
- }
- ]
- }
- }
- return state;
- }
TodoFt.js
- import React from "react";
- import {connect} from "react-redux";
- class TodoFt extends React.Component {
- constructor() {
- super();
- }
- render() {
- return <div>
- 当前:共{this.props.todos.length}条信息--
- 已做{this.props.todos.filter(item => item.done).length}条--
- 未做{this.props.todos.filter(item => !item.done).length}条
- </div>
- }
- }
- export default connect(
- (state)=>{
- return {
- todos:state.todos
- }
- }
- )(TodoFt)
示例代码
因为TodoItem这个组件是不通天的,所以TodoItem是不能自己独立dispatch到store的。
此时就需要TodoBd帮助,因为TodoBd是通天。
这是套路:所有被循环语句map出来的组件,一律不通天,数据父亲给,改变store的能力父亲给。
TodoBd.js组件引入了TodoItem.js组件,因为TodoItem组件是被map出来的,所以信息要传给每一个TodoItem,而不是让TodoItem自己通天拿数据。
TodoBd.js
- import React from 'react';
- import {connect} from "react-redux";
- import TodoItem from "./TodoItem.js";
- class TodoBd extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- //根据全局的show属性来决定当前todos数组
- if(this.props.show == "ALL"){
- var todos = this.props.todos;
- }else if(this.props.show == "ONLYDONE"){
- var todos = this.props.todos.filter(item=>item.done);
- }else if(this.props.show == "ONLYUNDONE"){
- var todos = this.props.todos.filter(item=>!item.done);
- }
- return (
- <div>
- {
- todos.map(item=>{
- return <TodoItem
- key={item.id}
- item={item}
- delTodo={this.props.delTodo.bind(this)}
- changeTodo={this.props.changeTodo.bind(this)}
- ></TodoItem>
- })
- }
- </div>
- );
- }
- }
- export default connect(
- (state)=>{
- return {
- todos : state.todos ,
- show : state.show
- }
- },
- (dispatch)=>{
- return {
- delTodo(id){
- dispatch({"type" : "DELTODO", id});
- },
- changeTodo(id , k , v){
- dispatch({"type" : "CHANGETODO", id, k, v});
- }
- }
- }
- )(TodoBd);
TodoItem.js
- import React from 'react';
- export default class TodoItem extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- "onEdit" : false
- }
- }
- render() {
- const {id, title, done} = this.props.item;
- return (
- <div>
- <input
- type="checkbox" checked={done}
- onChange={(e)=>{
- this.props.changeTodo(id, "done", e.target.checked)
- }}
- />
- {
- this.state.onEdit
- ?
- <input
- type="text"
- defaultValue={title}
- onBlur={(e)=>{
- this.props.changeTodo(id,"title", e.target.value)
- this.setState({"onEdit" : false})
- }}
- />
- :
- <span onDoubleClick={()=>{this.setState({"onEdit":true})}}>
- {title}
- </span>
- }
- <button onClick={()=>{this.props.delTodo(id)}}>删除</button>
- </div>
- );
- }
- }
index.js
- const initObj = {
- "todos" : [
- ...
- ],
- "show" : "ALL" //ALL、ONLYDONE、ONLYUNDONE
- }
- export default (state = initObj, action) => {
- if(action.type == "ADDTODO"){
- ...
- }else if(action.type == "DELTODO"){
- return {
- ...state,
- "todos" : state.todos.filter(item => item.id != action.id)
- }
- }else if(action.type == "CHANGETODO"){
- return {
- ...state,
- "todos" : state.todos.map(item => {
- //如果遍历到的item项和传入的aciton的id项不一样,此时返回原item
- if(item.id != action.id) return item;
- //否则返回修改之后的item
- return {
- ...item ,
- [action.k] : action.v
- }
- })
- }
- }else if(action.type == "CHANGESHOW"){
- return {
- ...state,
- "show" : action.show
- }
- }
- return state;
- }
TodoFt.js
- import React from 'react';
- import {connect} from "react-redux";
- import classnames from "classnames";
- class TodoFt extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <p>
- 当前共{this.props.todos.length}条信息
- 做完{this.props.todos.filter(item=>item.done).length}条
- 未做{this.props.todos.filter(item=>!item.done).length}条
- </p>
- <p>
- <button className={classnames({"cur":this.props.show == 'ALL'})}
onClick={()=>{this.props.changeShow('ALL')}}>查看全部- </button>
- <button className={classnames({"cur":this.props.show == 'ONLYDONE'})}
- onClick={()=>{this.props.changeShow('ONLYDONE')}}>仅看已做
- </button>
- <button className={classnames({"cur":this.props.show == 'ONLYUNDONE'})}
- onClick={()=>{this.props.changeShow('ONLYUNDONE')}}>仅看未做
- </button>
- </p>
- </div>
- );
- }
- }
- export default connect(
- (state) => {
- return {
- todos : state.todos ,
- show : state.show
- }
- },
- (dispatch) => {
- return {
- changeShow(show){
- dispatch({"type" : "CHANGESHOW" , show})
- }
- }
- }
- )(TodoFt);
四、logger插件
redux-logger用来辅助开发。
- npm install --save redux-logger
改变main.js:
- import React from "react";
- import ReactDOM from "react-dom";
- import {createStore , applyMiddleware} from "redux";
- import {Provider} from "react-redux";
- import logger from "redux-logger";
- import App from "./App.js";
- //引入reducer
- import reducer from "./reducers/index.js";
- //创建store
- const store = createStore(reducer , applyMiddleware(logger));
- ReactDOM.render(
- <Provider store={store}>
- <App></App>
- </Provider>
- ,
- document.getElementById("app-container")
- );
也可以使用redux-devtools这个插件。
- npm install --save-dev redux-devtools
- npm install --save-dev redux-devtools-log-monitor
- npm install --save-dev redux-devtools-dock-monitor
- npm install --save-dev redux-devtools-chart-monitor
文档:
https://github.com/reduxjs/redux-devtools
https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md
五、combineReducers和bindActionCreators
一个网页的应用程序可能是多个reducer,合并为一个reducer,比如counter和todo的reducer。
Redux提供的combineReducers方法,用于reducer的拆分,只要定义各个子reducer函数,然后用这个方法,将它们合成一个大的reducer。
Redux提供的bindActionCreators方法,用于通过dispatch将action包裹起来,这条可以通过bindActionCreators创建的方法,直接调用dispatch(action)“隐式调用”。
5.1 combineReducers
reducers/counter.js就是一个普通的纯函数:
- export default (state = {"v" : 10},action)=>{
- return state;
- }
reducers/todo.js提供的数据:
- const initObj = {
- "todos": [
- { "id": 1, "title": "吃饭", "done": false },
- { "id": 2, "title": "睡觉", "done": false },
- { "id": 3, "title": "打豆豆", "done": true }
- ]
- };
- export default (state = initObj, action) => {
- return state
- }
示例代码
reducers/index.js要用redux提供的combineReducers来进行智能合并
- import { combineReducers } from "redux";
- import counter from "./counter.js";
- import todos from "./todos.js";
- //暴露合并的reducer
- export default combineReducers({
- counter,
- todos
- })
main.js
- import React from "react";
- import ReactDOM from "react-dom";
- import {createStore} from "redux";
- import {Provider} from "react-redux";
- import App from "./containers/App.js";
- //引入reducer
- import reducer from "./reducers";
- // 创建 Redux store 来存放应用的状态。
- const store = createStore(reducer);
- ReactDOM.render(
- <Provider store={store}>
- <App></App>
- </Provider>,
- document.getElementById("app")
- );
containers/App.js组件使用数据
- import React from 'react';
- import {connect} from "react-redux";
- class App extends React.Component {
- constructor(){
- super();
- }
- render() {
- return (
- <div>
- <h1>{this.props.v}</h1>
- </div>
- );
- }
- }
- export default connect(
- ({counter}) => ({
- v : counter.v
- })
- )(App);
components/TodoList/index.js组件
- import React from "react";
- import { connect } from "react-redux";
- class TodoList extends React.Component {
- constructor(){
- super();
- }
- render() {
- return (
- <div>
- <h1>我是TodoList</h1>
- {
- this.props.todos.map(item=>{
- return <p key={item.id}>{item.title}</p>
- })
- }
- </div>
- )
- }
- };
- export default connect(
- ({todos: {todos}})=>{
- return {
- todos
- }
- }
- )(TodoList)
containers/App.js引入组件:
- import React from 'react';
- import TodoList from "../components/todolist/index.js";
- import Counter from "../components/counter/index.js";
- export default class App extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <Counter></Counter>
- <TodoList></TodoList>
- </div>
- );
- }
- }
5.2 bindActionCreators
bindActionCreators会将action和dispatch绑定并返回一个对象,这个对象会作为props的一部分传入组件中。
bindActionCreators主要作用:一般情况下,可以通过Provider将store通过React的connext属性向下传递,bindActionCreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。
官方的文件夹结构:https://github.com/reduxjs/redux/tree/master/examples/todomvc/src
actions/counterActions.js:新建actions文件夹存放type
- // 我们把return一个action的函数叫做“action creator”
- // 所以这个文件向外暴露了几个动作
- export const add = () => ({ type: "ADD" })
- export const minus = () => ({ type: "MINUS" })
- export const cheng = () => ({ type: "CHENG" })
- export const chu = () => ({ type: "CHU" })
counter/index.js计数器组件
- import React from 'react';
- import {bindActionCreators} from "redux";
- import {connect} from "react-redux";
- import * as counterActions from "../../actions/counterActions.js";
- class Counter extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <h1>Counter : {this.props.v}</h1>
- <button onClick={()=>{this.props.counterActions.add()}}>加</button>
- <button onClick={()=>{this.props.counterActions.minus()}}>减</button>
- <button onClick={()=>{this.props.counterActions.cheng()}}>乘</button>
- <button onClick={()=>{this.props.counterActions.chu()}}>除</button>
- </div>
- );
- }
- }
- export default connect(
- ({counter}) => ({
- v : counter.v
- }),
- (dispatch) => ({
- //这里的dispatch,等同于store中的store.dispatch,用于组合action
- counterActions : bindActionCreators(counterActions , dispatch)
- })
- )(Counter);
app/reducers/counter.js:
- import {ADD, MINUS, CHENG, CHU} from "../constants/COUNTER.js";
- export default (state = {"v" : 0} , action) => {
- if(action.type == "ADD"){
- return {
- ...state ,
- "v" : state.v + 1
- }
- }else if(action.type == "MINUS"){
- return {
- ...state ,
- "v" : state.v - 1
- }
- }else if(action.type == "CHENG"){
- return {
- ...state ,
- "v" : state.v * 2
- }
- }else if(action.type == "CHU"){
- return {
- ...state ,
- "v" : state.v / 2
- }
- }
- return state;
- }
示例代码
todolist/index.js
- import React from 'react';
- import TodoHd from "./TodoHd.js";
- import TodoBd from "./TodoBd.js";
- export default class TodoList extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <h1>TodoList</h1>
- <TodoHd></TodoHd>
- <TodoBd></TodoBd>
- </div>
- );
- }
- }
TodoHd.js
- import React from 'react';
- import {bindActionCreators} from "redux";
- import {connect} from "react-redux";
- import * as todoActions from "../../actions/todoActions.js";
- class TodoHd extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- <input type="text" ref="titleTxt"/>
- <button onClick={()=>{this.props.todoActions.add(this.refs.titleTxt.value)}}
- >添加
- </button>
- </div>
- );
- }
- }
- export default connect(
- null ,
- (dispatch) => ({
- todoActions : bindActionCreators(todoActions , dispatch)
- })
- )(TodoHd);
TodoBd.js
- import React from 'react';
- import {bindActionCreators} from "redux";
- import {connect} from "react-redux";
- import * as todoActions from "../../actions/todoActions.js";
- class TodoBd extends React.Component {
- constructor(props) {
- super(props);
- }
- render() {
- return (
- <div>
- {
- this.props.todos.map(item=>{
- return <p key={item.id}>
- {item.title}
- <button onClick={()=>{this.props.todoActions.del(item.id)}}>
- 删除
- </button>
- </p>
- })
- }
- </div>
- );
- }
- }
- export default connect(
- ({todo}) => ({
- todos : todo.todos
- }) ,
- (dispatch) => ({
- todoActions : bindActionCreators(todoActions , dispatch)
- })
- )(TodoBd);
为了防止action的type命名冲突,此时要单独存放在const文件夹中:
app\constants\COUNTER.js
- export const ADD = "ADD_COUNTER";
- export const MINUS = "MINUS_COUNTER";
- export const CHENG = "CHENG_COUNTER";
- export const CHU = "CHU_COUNTER";
app\constants\TODO.js
- export const ADD = "ADD_TODO";
- export const DEL = "DEL_TODO";
然后就可以在以下文件中,引入以上常量,然后使用大写的常量替换type字符串
l actions中的counterActions.js和todoActions.js
l reducers中的todo.js和counter.js
actions/TodoActions.js
- import {ADD , DEL} from "../constants/TODO.js";
- export const add = (title) => ({"type" : ADD, title});
- export const del = (id) => ({"type" : DEL, id});
actions/counterActions.js:
- import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
- export const add = () => ({"type" : ADD});
- export const minus = () => ({"type" : MINUS});
- export const cheng = () => ({"type" : CHENG});
- export const chu = (n) => ({"type" : CHU , n});
reducers/todo.js
- import {ADD , DEL} from "../constants/TODO.js";
- const initObj = {
- "todos" : [
- {"id" : 1 , "title" : "吃饭" , "done" : false},
- {"id" : 2 , "title" : "睡觉" , "done" : false},
- {"id" : 3 , "title" : "打豆豆" , "done" : false}
- ]
- }
- export default (state = initObj , action) => {
- if(action.type == ADD){
- return {
- ...state ,
- "todos" : [
- ...state.todos ,
- {
- "id" : state.todos.reduce((a,b)=>{return b.id > a ? b.id : a},0) + 1,
- "title": action.title,
- "done" : action.done
- }
- ]
- }
- }else if(action.type == DEL){
- return {
- ...state ,
- "todos" : state.todos.filter(item => item.id != action.id)
- }
- }
- return state;
- }
reducers/counter.js
- import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
- export default (state = {"v" : 0} , action) => {
- if(action.type == ADD){
- return {
- ...state ,
- "v" : state.v + 1
- }
- }else if(action.type == MINUS){
- return {
- ...state ,
- "v" : state.v - 1
- }
- }else if(action.type == CHENG){
- return {
- ...state ,
- "v" : state.v * 2
- }
- }else if(action.type == CHU){
- return {
- ...state ,
- "v" : state.v / action.n
- }
- }
- return state;
- }
前端笔记之React(五)Redux深入浅出的更多相关文章
- 读书笔记之第五回深入浅出关键字---把new说透
第五回深入浅出关键字---把new说透 ------你必须知道的.net读书笔记 new一个class时,new完成了以下两个方面的内容:一是调用newobj命令来为实例在托管堆中分配内存:二是调用 ...
- 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发
一.React生命周期 一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期. 生命周期这个东西,必须有项目,才知道他们干嘛的. 1.1 Mouting阶段[装载过程] 这个阶 ...
- 前端笔记之React(六)ES6的Set和Map&immutable和Ramda和lodash&redux-thunk
一.ES6的Set.Map数据结构 Map.Set都是ES6新的数据结构,都是新的内置构造函数,也就是说typeof的结果,多了两个: Set 是不能重复的数组 Map 是可以任何东西当做键的对象 E ...
- 前端笔记之React(八)上传&图片裁切
一.上传 formidable天生可以处理上传的文件,非常简单就能持久上传的文件. 今天主要讲解的是,前后端的配合套路. 上传分为同步.异步.同步公司使用非常多,异步我们也会讲解. 1.1 先看一下a ...
- 前端笔记之React(七)redux-saga&Dva&路由
一.redux-saga解决异步 redux-thunk 和 redux-saga 使用redux它们是必选的,二选一,它们两个都可以很好的实现一些复杂情况下redux,本质都是为了解决异步actio ...
- 前端笔记之React(一)初识React&组件&JSX语法
一.React项目起步配置 官网:https://reactjs.org/ 文档:https://reactjs.org/docs/hello-world.html 中文:http://react.c ...
- 前端笔记:React的form表单全部置空或者某个操作框置空的做法
1.全部置空的做法,一般在弹出框关闭后,需要重置该form所有表单: this.props.form.resetFields(); 2.针对某个操作框置空的做法 例如,form表单里有一个部门和一个张 ...
- 前端笔记之React(二)组件内部State&React实战&表单元素的受控
一.组件内部的State 1.1 state state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state. state是一个对象,什么值都可以定义. 在任何类式组件的构造函数中,可以用 ...
- 前端笔记之React(三)使用动态样式表&antd&React脚手架&props实战
一.使用动态样式表 1.1 LESS使用 全局安装Less npm install -g less 创建1.less文件,然后可以用lessc命令来编译这个文件: lessc 1.less 1.css ...
随机推荐
- jdk9新特性之jShell
jdk9还没研究完,结果jdk10都停止维护了. 最近回顾jdk9,发现了一个新特性--jShell. jdk9是在2017年的9月份发布的,这是我开始感觉入门java的时间.从jdk10开始就是半年 ...
- ThinkPHP判断post,get操作
define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']); define('IS_GET', REQUEST_METHOD =='GET' ? true ...
- Java学习笔记——Linux下安装配置tomcat
朝辞白帝彩云间,千里江陵一日还. 两岸猿声啼不住,轻舟已过万重山. ——早发白帝城 首先需要安装配置JDK,这里简单回顾下.Linux下用root身份在/opt/文件夹下创建jvm文件夹,然后使用ta ...
- PATB 1028. 人口普查(20)
1028. 人口普查(20) 注意特判合理人数为0,否则格式错误.很暴力的sort排序找出最大最小. 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Stan ...
- SCIgen与野鸡期刊的梗
layout: post title: "SCIgen与野鸡期刊的梗" date: 2019-04-28 19:06:21 +0800 --- 作者:吴甜甜 个人博客网站: wut ...
- 关于火狐浏览器设置cookie的一个问题
最近发现我一个项目的网页,里面的cookie无法添加了,急的我瞪着我的PHP代码沉思了好久,我默认用的火狐浏览器,然而我默默的打开另一个叫360的浏览器,发现它的cookie是正常添加的. ... 难 ...
- 数据库root密码删除
1 打开mysql.exe和mysqld.exe所在的文件夹,复制路径地址 2 打开cmd命令提示符,进入上一步mysql.exe所在的文件夹. 3 输入命令 mysqld --skip-g ...
- myecliese加大内存
加大内存代码 : -Xms512m -Xmx1024m -XX:PermSize=256M -XX:MaxPermSize=1024m
- wcf服务编程(一)
步骤一:定义契约 [ServiceContract] //定义服务契约 需要引用System.ServiceModel public interface ICalculator { [Operatio ...
- 自定义HashSet判重标准
HashSet在执行add时会首先根据目标类的hashcode判断是否有与其hashcode相同的对象,若有则使用equals该对象判断是否与其相同. HashSet保证了元素的唯一性, 我们可以通过 ...