React-Redux应用

162 阅读9分钟

1、拆分文件

redux整个流程(简版):

1、先在action-types.js里定功能

2、组件

3、reducer

4、合并reducer

5、产生store

6、渲染组件并运行文件

安装redux

yarn add redux

目录:

src/components/Counter.js

import React,{Component} from 'react';
import store from '../store';
export default class Counter extends React.Component {
    render(){
        return (
            <div>{store.getState().counter.num}</div>
        )
    }
}

src/components/Todo.js

import React,{Component} from 'react';
import store from '../store';
export default class Todo extends React.Component {
    render(){
        return (
            <div>TODO</div>
        )
    }
}

src/store/reducer/counter.js

import * as Types from '../action-types'
function counter(state={num:100},action) {
    switch (action.type){
        case Types.INCREMENT:
            return {num:state.num+action.count}
        case Types.DECREMENT:
            return {num:state.num-action.count}
    }
    return state;
}




// 这里由于counter.js是专门用来处理counter的所以可以默认导出
export default counter

src/store/reducer/todo.js

import * as Types from '../action-types';
function todo(state=[],action) {
    switch (action.type){
        case Types.ADD_TODO:
            return [...state,action.text];
    }
    return state;
}
export default todo;

src/store/reducer/index.js

// 合并reducer
import counter from './counter';
import todo from './todo';
import {combineReducers} from "redux";

// 会产生一个新的reducer
let reducers = combineReducers({
    counter,todo
});// {counter:{num:0},todo:[]}

export default reducers;

src/store/action-types.js

// 通过export导出   import * as Types form './action-types'引入  通过Types.INCREMENT引用

export const INCREMENT = 'INCREMENT';//第一个要写的功能是增加
export const DECREMENT = 'DECREMENT';//第二个要写的功能是减少

//这里我们将todo的逻辑和计数器的逻辑写一起  因为就只有这三个功能
// 功能多,逻辑多的时候就拆分开写

export const ADD_TODO = 'ADD_TODO';

src/store/index.js

import reducer from './reducer';
import {createStore} from "redux";


//创建一个store
export default createStore(reducer);

src/index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Counter from './components/Counter';

ReactDOM.render(<div>
    <Counter/>
</div>,window.root);

流程图:

计数器和todolist列子

目录:

src/components/Counter.js

import React,{Component} from 'react';
import store from '../store';
import * as actions from '../store/action/counter'
// import * as Types from '../store/action-types';
// // ActionCreator 就是一个普通函数 返回一个action对象 这段代码抽出到action counter.js中去
// function add(count) {
//     return {type:Types.INCREMENT,count}
// }
// function minus(count) {
//     return {type:Types.DECREMENT,count}
// }
export default class Counter extends React.Component {
    constructor(){
        super();
        this.state = {n:store.getState().counter.num}
    }
    componentDidMount(){
        store.subscribe(()=>{
            this.setState({n:store.getState().counter.num});
        })
    }
    render(){
        return (
            <div>
                <button onClick={()=>{
                    store.dispatch(actions.add(10));
                }}>+</button>
                {/*{store.getState().counter.num}*/}
                {this.state.n}
                <button onClick={()=>{
                    store.dispatch(actions.minus(10));
                }}>-</button>
            </div>
        )
    }
}

src/components/Todo.js

import React,{Component} from 'react';
import store from '../store';
import actions from '../store/action/todo';
export default class Todo extends React.Component {
    constructor(){
        super();
        this.state = {todos:store.getState().todo}
    }
    componentDidMount(){
        store.subscribe(()=>{
            this.setState({todos:store.getState().todo})
        })
    }
    render(){
        return (
            <div>
                <input type="text" onKeyUp={(e)=>{
                    if(e.keyCode == 13){
                        store.dispatch(actions.addTodo(e.target.value));
                    }
                }}/>
                {this.state.todos.map((item,index)=>(<li key={index}>{item}</li>))}
            </div>
        )
    }
}

src/store/action-types.js

// 通过export导出   import * as Types form './action-types'引入

export const INCREMENT = 'INCREMENT';//第一个要写的功能是增加
export const DECREMENT = 'DECREMENT';//第二个要写的功能是减少

//这里我们将todo的逻辑和计数器的逻辑写一起  因为就只有这三个功能
// 功能多,逻辑多的时候就拆分开写

export const ADD_TODO = 'ADD_TODO';

src/store/index.js

import reducer from './reducer';
import {createStore} from "redux";





//创建一个store
export default createStore(reducer);

src/store/reducer/counter.js

import * as Types from '../action-types'
function counter(state={num:100},action) {
    switch (action.type){
        case Types.INCREMENT:
            return {num:state.num+action.count}
        case Types.DECREMENT:
            return {num:state.num-action.count}
    }
    return state;
}




// 这里由于counter.js是专门用来处理counter的所以可以默认导出
export default counter

src/store/reducer/todo.js

import * as Types from '../action-types';
function todo(state=[],action) {
    switch (action.type){
        case Types.ADD_TODO:
            return [...state,action.text];
    }
    return state;
}
export default todo;

src/store/reducer/index.js

// 合并reducer
import counter from './counter';
import todo from './todo';
import {combineReducers} from "redux";

// combineReducers的实现原理
// 会产生一个新的reducer

// let myCombine = (reducers)=>{ 
// //参数reducers就是combineReducers里的对象{counter,todo} counter和todo是函数
//     return (state={},action)=>{
//         let obj = {}
//         for (let key in reducers){
//             obj[key] = reducers[key](state[key],action)
//         }
//         return obj
//     }
// };


let reducers = combineReducers({
    counter,todo
});// {counter:{num:0},todo:[]}

export default reducers;

src/store/action/counter.js

import * as Types from '../action-types';
// ActionCreator  就是一个普通函数  返回一个action对象
function add(count) {
    return {type:Types.INCREMENT,count}
}
function minus(count) {
    return {type:Types.DECREMENT,count}
}
export {add,minus}

src/store/action/todo.js

import * as Types from '../action-types';

let obj = {
    addTodo(content){
       return {type:Types.ADD_TODO,text:content}
    }
};
export default obj;

src/index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Counter from './components/Counter';
import Todo from "./components/Todo";

ReactDOM.render(<div>
    <Counter/>
    <Todo/>
</div>,window.root);

2、引入react-redux

在以上:计数器和todolist列子之上

由于每次需要把状态变成自己的,再去改这个状态,就很麻 => 如图

所以:安装 yarn add react-redux

react-redux提供了一个Provider组件,使用这个组件

src/index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Counter from './components/Counter';
import Todo from "./components/Todo";


// 1、react-redux提供了一个Provider组件 这个组件需要传入store  所以引入react-redux和store
import store from './store';
import {Provider} from 'react-redux';

// 2、使用Provider组件,这个组件只能有一个元素  传入store
ReactDOM.render(<Provider store={store}>
    <div>
        <Counter/>
        <Todo/>
    </div>
</Provider>,window.root);

进到:src/components/Counter.js

import React,{Component} from 'react';

// 1、组件外面(index.js的Provider)传入了stroe,所以不需要引入
// import store from '../store';

import * as actions from '../store/action/counter'

// 3、需要引入connect连接的方法取出redux中的数据
import {connect} from 'react-redux';



// 4、利用react-redux需要导出一个连接后的组件
// export default class Counter extends React.Component {
    class Counter extends React.Component {

    // 2、状态和componentDidMount都不需要 所以渲染render的state部分也都注释
    // constructor(){
    //     super();
    //     this.state = {n:store.getState().counter.num}
    // }
    // componentDidMount(){
    //     store.subscribe(()=>{
    //         this.setState({n:store.getState().counter.num});
    //     })
    // }


    render(){
        return (
            <div>
                <button onClick={()=>{
                    // store.dispatch(actions.add(10));
                    this.props.add(1);
                }}>+</button>
                {/* {this.state.n} */}
                {/* <p>{this.props.n1}</p> */}
                <p>{this.props.num}</p>
                <button onClick={()=>{
                    // store.dispatch(actions.minus(10));
                    this.props.minus(2);
                }}>-</button>
            </div>
        )
    }
}



// // 4、导出一个连接后的组件  开始
// // connect执行时有两个“函数” mapStateToProps、mapDispatchToProps
// // 4-1、mapStateToProps    将redux中的状态映射成属性    传递给第二个参数(当前组件)
// // 4-2、mapDispatchToProps 将dispatch方法映射成属性     传递给第二个参数(当前组件)
// // 4-3、这两个组件的返回值会作为当前组件的属性
// let mapStateToProps = (state) =>{ // 这个函数的参数是state
//     return {n1:state.counter.num}
//     // return {...state.counter} // {...state.counter} = > {num:0}
// };
// let mapDispatchToProps = (dispatch) =>{ // 这个函数的参数是dispatch
//     return {
//         add:(count)=>{
//             dispatch(actions.add(count));
//         },
//         minus:(count)=>{
//             dispatch(actions.minus(count));
//         }
//     }
// };

// export default connect(mapStateToProps,mapDispatchToProps)(Counter);// 第二次执行的参数是当前组件
// // 4、导出一个连接后的组件  结束



// 5、简化4的写法
// connect中的mapDispatchToProps可以传入一个actionCreator对象
export default connect(state=>({...state.counter}),actions)(Counter);// 第二次执行的参数是当前组件


// // 5_1、中actiopns是如何实现,去匹配mapDispatchToProps中的函数(使用actions后,mapDispatchToProps中返回的函数名add和minus不能改名)
// let bindActionCreators = (actions) =>{//为什么可以直接传入一个actions,在内部会用这个函数进行包装
//     return(dispatch)=>{
//         let obj = {};
//         for(let key in actions){
//             obj[key] = (...args)=>{
//                 dispatch(actions[key](...args))
//             }
//         }
//         return obj;
//     }
// }
// export default connect(state=>({...state.counter}),bindActionCreators(actions))(Counter);
// // 5_1、以上这一段代码就是4、 mapDispatchToProps 中的实现也是5、中直接传入actions的实现  这个方法时redux中的

进到:src/components/Todo.js

import React,{Component} from 'react';
// 1、组件外面(index.js的Provider)传入了stroe,所以不需要引入
// import store from '../store';
import actions from '../store/action/todo';
// 3、需要引入connect连接的方法取出redux中的数据
import {connect} from 'react-redux';
// 4、利用react-redux需要导出一个连接后的组件
// export default class Todo extends React.Component {
     class Todo extends React.Component {
    // 2、状态和componentDidMount都不需要 所以渲染render的state部分也都注释
    // constructor(){
    //     super();
    //     this.state = {todos:store.getState().todo}
    // }
    // componentDidMount(){
    //     store.subscribe(()=>{
    //         this.setState({todos:store.getState().todo})
    //     })
    // }
    render(){
        return (
            <div>
                <input type="text" onKeyUp={(e)=>{
                    if(e.keyCode == 13){
                        // store.dispatch(actions.addTodo(e.target.value));
                        this.props.addTodo(e.target.value);
                        e.target.value = " "
                    }
                }}/>
                {/* {this.state.todos.map((item,index)=>(<li key={index}>{item}</li>))} */}
                {this.props.todos.map((item,index)=>(<li key={index}>{item}</li>))}
            </div>
        )
    }
}

// {counter:{num:0},todo:[]}
// 这里todo:[]是数组,不能解构,只有通过 => 把状态里的todo映射到Todo组件的todos属性里
export default connect(state=>({todos:state.todo}),actions)(Todo)
//4、导出一个连接后的组件  结束





// // 4_1、中actiopns是如何实现,去匹配mapDispatchToProps中的函数(使用actions后,mapDispatchToProps中返回的函数名add和minus不能改名)
// let bindActionCreators = (actions) =>{//为什么可以直接传入一个actions,在内部会用这个函数进行包装
//     return(dispatch)=>{
//         let obj = {};
//         for(let key in actions){
//             obj[key] = (...args)=>{
//                 dispatch(actions[key](...args))
//             }
//         }
//         return obj;
//     }
// }
// export default connect(state=>({...state.counter}),bindActionCreators(actions))(Counter);
// // 4_1、以上这一段代码就是4、 mapDispatchToProps 中的实现也是5、中直接传入actions的实现  这个方法时redux中的

除以上代码,其他代码逻辑不变。

3-6、react-redux之todo

目录

src/components/App.js

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TodoHeader from './TodoHeader';
import TodoList from './TodoList';
import TodoFooter from './TodoFooter';
export default class App extends React.Component {
    constructor() {
        super();
    }
    render() {
        return (
            <div className='container'>
                <div className='row'>
                    <div className='col-md-6 col-md-offset-3'>
                        <div className='panel panel-danger'>
                            <div className='panel-heading'><TodoHeader /></div>
                            <div className='panel-body'><TodoList /></div>
                            <div className='panel-footer'><TodoFooter /></div>
                        </div>
                    </div>
                </div>

            </div>

        )
    }
}

src/components/TodoFooter.js

import React, { Component } from 'react';
import {connect} from 'react-redux';
import actions from '../store/action/index';
class TodoFooter extends React.Component {
    render() {
        return (
            <div>
                {/* {this.props.type} // 测试 */}
                <nav className='nav nav-pills' onClick={(e)=>{
                    let result = e.target.dataset.type;//点击的某一个
                    this.props.changeType(result);
                }}>
                    <li className={this.props.type==='all'?'active':''}><a data-type='all'>全部</a></li>
                    <li className={this.props.type==='unfinish'?'active':''}><a data-type='unfinish'>未完成</a></li>
                    <li className={this.props.type==='finish'?'active':''}><a data-type='finish'>已完成</a></li>
                </nav>
            </div>
        )
    }
}
export default connect(state=>({...state}),actions)(TodoFooter);

src/components/TodoHeader.js

import React, { Component } from 'react';
import {connect} from 'react-redux';
// actions是actionCreator组成的对象
import actions from '../store/action/index'
class TodoHeader extends React.Component {
    getUnFinishCount = () =>{ // 过滤数组中没有选中的length
        return this.props.todos.filter(item=>!item.isSelected).length
    }
    render() {
        return <div >
            <h3>亲 你有{this.getUnFinishCount()}件事没完成 </h3>
            <input type='text' className='form-control' onKeyUp={(e)=>{
            if(e.keyCode === 13){
                this.props.addTodo({id:Math.random(),title:e.target.value,
                    isSelected:false})
            }
            }}/>
        </div>
    }
}
// store.getState();//{todos:[{title:'xxx'}]}
export default connect(state=>({...state}),actions)(TodoHeader)

src/components/TodoList.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import actions from '../store/action/index';
import TodoItem from './TodoItem';
class TodoList extends React.Component {
    filterData() {
        let todos = [];
        if (this.props.type === 'all') {
            todos = this.props.todos
        } else if (this.props.type === 'finish') {
            todos = this.props.todos.filter(item => item.isSelected)
        } else {
            todos = this.props.todos.filter(item => !item.isSelected)
        }
        return todos;
    }
    render() {
        return <div>
            <ul className='list-group'>

                {/* <li className='list-group-item'>
                    <input type='checkbox'/>
                    今天吃药了吗?
                    <button className='btn btn-xs pull-right'>&times;</button>
                </li> */}

                {/* {this.props.todos.map((item,index)=>( */}
                {this.filterData().map((item, index) => (
                    <TodoItem 
                    key={index} 
                    item={item}
                    changeSelected={(id)=>{
                        this.props.changeSelected(id);
                    }}
                    deleteTodo={(id)=>{
                        this.props.deleteTodo(id);
                    }}
                    />
                ))}
            </ul>
        </div>
    }
}
// 一般情况下  组件分为两类  智能组件(聪明组件),通过redux获取数据  
// 木偶组件(傻的组件,ui组件),只负责展示
export default connect(state => ({ ...state }), actions)(TodoList);

src/components/TodoItem.js

import React, { Component } from 'react';
export default class App extends Component {
    render() {
        let {item} = this.props;
        return <li className='list-group-item'>
        <input type='checkbox' checked={item.isSelected} onChange={() => {
            this.props.changeSelected(item.id);
        }} />
        {item.title}
        <button className='btn btn-xs pull-right' onClick={() => {
            this.props.deleteTodo(item.id);
        }}>&times;</button>
    </li>
    }
}

src/store/action/index.js

import * as Types from '../action-types';
// actionCreator的对象
let actions = {
    addTodo(todo){ // todo是要添加的内容{title,id,isSelected}
        return {type:Types.ADD_TODO,todo}
    },
    changeSelected(id){ // 告诉我当前是哪个checkbox更改了
        return {type:Types.CHANGE_SELECTED,id}
    },
    deleteTodo(id){
        return {type:Types.DELETE_TODO,id}
    },
    changeType(val){
        return {type:Types.CHANGE_CURRENT_TYPE,val}
    }
};
export default actions;

src/store/reducer/index.js

import * as Types from '../action-types'
// 管理员
let initState = {
    type:'all',//默认全部显示
    todos:[
        {isSelected:false,title:'今天吃药了吗?',id:1},
        {isSelected:true,title:'今天吃药了吗?',id:2}
    ]
};
function reducer(state=initState,action){
    switch (action.type){
        case Types.ADD_TODO:
            return {...state,todos:[...state.todos,action.todo]};
        case Types.CHANGE_SELECTED:
        let todos = state.todos.map(item=>{
            if(item.id === action.id){// 找到id和当前选择id相等一项的
                item.isSelected = !item.isSelected;// 将状态取反
            }
            return item;
        })
            return {...state,todos}
        case Types.DELETE_TODO:{
            let todos = state.todos.filter(item=>item.id!=action.id);
            return {...state,todos}
        }
        case Types.CHANGE_CURRENT_TYPE:
            return {...state,type:action.val}
    }
    return state;
}
export default reducer;

src/store/action-types.js

export const ADD_TODO = 'ADD_TODO';// 添加
export const CHANGE_SELECTED = 'CHANGE_SELECTED';// 改变选择状态
export const DELETE_TODO = 'DELETE_TODO';// 删除某个todo
export const CHANGE_CURRENT_TYPE = 'CHANGE_CURRENT_TYPE';

src/store/index.js

import {createStore} from 'redux';
import reducer from './reducer';

let store = createStore(reducer);
window._store = store;
// 为了在控制台里可以打印store中的内容
export default store;

src/index.js

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import App from "./components/App";
import 'bootstrap/dist/css/bootstrap.css';

import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>
,window.root);

最后