Redux进阶

1.UI组件与容器组件的拆分

  • UI组件业界称为傻瓜组件[只负责页面显示],容器组件业界称为聪明组件[不管UI长成什么样子,关注业务逻辑]

  • UI组件负责页面的渲染,容器组件负责页面逻辑

  • 将TodoList组件拆分成TodoList的容器组件和TodoListUI的UI组件

    示例代码:

    //[TodoList.js]
    import React ,{Component} from 'react';
    import store from './store/index'; //也可以直接写成 import store from './store' 会默认直接找index.js
    import {getInputChangeAction,getAddItemAction,getDeleteItemAction} from './store/actionCreators'
    import TodoListUI from './TodoListUI';
    
    class Todolist extends Component{
        constructor(props) {
          super(props);
          //console.log(store.getState()); //store的方法,用于获取store中的数据
          this.state=store.getState();
          console.log(this.state);
          this.handleInputChange = this.handleInputChange.bind(this);
          this.handleStoreChange = this.handleStoreChange.bind(this);
          this.handleButtonClick = this.handleButtonClick.bind(this);
          this.handleItemDelete = this.handleItemDelete.bind(this);
          store.subscribe(this.handleStoreChange);//将handleStoreChange函数订阅给store,每当store中的数据发生变化的时候将调用此函数
        }
    
        render(){
            return(
                <TodoListUI 
                    inputValue = {this.state.inputValue}
                    handleInputChange = {this.handleInputChange}
                    handleButtonClick = {this.handleButtonClick}
                    list = {this.state.list}
                    handleItemDelete = {this.handleItemDelete}
                />
            )
        }
        handleInputChange(e){
            //创建action
            const action = getInputChangeAction(e.target.value);
            store.dispatch(action); //将action传递给store
        }
        handleStoreChange(){
            this.setState(store.getState());//当store发生变化时,则调用此方法。用store中的数据替换当前组件的state数据。
        }
        handleButtonClick(){
            const action = getAddItemAction();
            store.dispatch(action);
        }
        handleItemDelete(index){
            const action=getDeleteItemAction(index);
            store.dispatch(action);
        }
    }
    export default Todolist;
    
    //[TodoListUI.js]
    import React,{Component} from 'react';
    import 'antd/dist/antd.css';//antd是全局变量
    import {Input,Button,List} from 'antd';
    
    class TodoListUI extends Component{
        render(){
            return(
                <div style={{marginTop:'10px',marginLeft:"10px"}}>
                    <div>
                        {/*输入框组件*/}
                        <Input placeholder="todoInfo" onChange={this.props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={this.props.inputValue}/>
                        {/*按钮组件*/}
                        <Button type="primary" onClick={this.props.handleButtonClick}>提交</Button>
                        {/*列表组件
                            header列表头
                            footer列表底
                            bordered列表有边框
                            dataSource列表渲染的数据资源
                            renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容
                        */}
                        <List
                            header={<div>header</div>}
                            footer={<div>footer</div>}
                            bordered
                            dataSource={this.props.list}
                            renderItem={(item,index)=>(<List.Item onClick={(index) => {this.props.handleItemDelete(index)}}>{item}</List.Item>)}
                            style={{marginTop:'10px',width:'300px'}}
                        />
                    </div>
                </div>
            )
        }
    }
    export default TodoListUI;

2.无状态组件

  • 什么是无状态组件?

    • 当组件只有render函数时,可以用无状态组件替换该组件
    • 无状态组件是一个函数
    • 无状态组件的性能比较高,因为就是一个函数;而普通的组件是一个类,需要执行所有生命周期函数
    • 适用时刻:一般当定义UI组件时,只做页面渲染而不做逻辑操作时,就可以将该组件用无状态组件替换
  • 示例代码:

    //[替换前TodoListUI.js]
    import React,{Component} from 'react';
    import 'antd/dist/antd.css';//antd是全局变量
    import {Input,Button,List} from 'antd';
    class TodoListUI extends Component{
        render(){
            return(
                <div style={{marginTop:'10px',marginLeft:"10px"}}>
                    <div>
                        {/*输入框组件*/}
                        <Input placeholder="todoInfo" onChange={this.props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={this.props.inputValue}/>
                        {/*按钮组件*/}
                        <Button type="primary" onClick={this.props.handleButtonClick}>提交</Button>
                        {/*列表组件
                            header列表头
                            footer列表底
                            bordered列表有边框
                            dataSource列表渲染的数据资源
                            renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容
                        */}
                        <List
                            header={<div>header</div>}
                            footer={<div>footer</div>}
                            bordered
                            dataSource={this.props.list}
                            renderItem={(item,index)=>(<List.Item onClick={(index) => {this.props.handleItemDelete(index)}}>{item}</List.Item>)}
                            style={{marginTop:'10px',width:'300px'}}
                        />
                    </div>
                </div>
            )
        }
    }
    export default TodoListUI;
    
    //[替换后TodoListUI.js]
    import React from 'react';
    import 'antd/dist/antd.css';//antd是全局变量
    import {Input,Button,List} from 'antd';
    const TodoListUI = (props)=>{
        return(
                <div style={{marginTop:'10px',marginLeft:"10px"}}>
                    <div>
                        {/*输入框组件*/}
                        <Input placeholder="todoInfo" onChange={props.handleInputChange} style={{width:'300px',marginRight:'10px'}} value={props.inputValue}/>
                        {/*按钮组件*/}
                        <Button type="primary" onClick={props.handleButtonClick}>提交</Button>
                        {/*列表组件
                            header列表头
                            footer列表底
                            bordered列表有边框
                            dataSource列表渲染的数据资源
                            renderItem怎么渲染,dataSource中的每一个数据都会调用renderItem中的函数进行渲染,将每一条数据渲染成List.Item的组件,数据的内容就是dataSource中每一项数据的内容
                        */}
                        <List
                            header={<div>header</div>}
                            footer={<div>footer</div>}
                            bordered
                            dataSource={props.list}
                            renderItem={(item,index)=>(<List.Item onClick={() => {props.handleItemDelete(index)}}>{item}</List.Item>)}
                            style={{marginTop:'10px',width:'300px'}}
                        />
                    </div>
                </div>
            )
    };
    export default TodoListUI;

3.Redux中发送异步请求获取数据

  • 利用Charles工具设置拦截的请求:Tools->Map local->Add并添加拦截的路径和返回的数据->OK

  • 在项目内安装axios依赖:yarn add axios

  • 在TodoList组件中引入axios

    import axios from 'axios';
  • 在TodoList组件的生命周期componentDidMount中利用axios发送ajax请求获取数据

    //引入action事件
    import {initListAction} from './store/actionCreators'
    //[TodoList.js部分代码]
    componentDidMount(){
            axios.get("/list.json")
                .then((res)=>{
                    const data = res.data;
                    const action = initListAction(data);
                    store.dispatch(action);
                });
        }
  • 在actionTypes.js中添加action类型常量

    //[store/actionTypes.js]
    export const INIT_LIST_ACTION = "init_list_action";
  • 在actionCreators.js中添加action传递信息

    //[store/actionCreators.js]
    //引入action类型常量
    import {INIT_LIST_ACTION} from './actionTypes';
    //添加action事件
    export const initListAction = (data)=>({
        type:INIT_LIST_ACTION,
        data
    })
  • 在reducer.js中添加对应action的处理方法

    //引入action类型常量
    import {INIT_LIST_ACTION} from './actionTypes';
    //[store/reducer.js]中的默认导出方法中添加处理内容
    else if(action.type === INIT_LIST_ACTION){
            const newState =  JSON.parse(JSON.stringify(state));
            newState.list=[...newState,...action.data];
            return newState;
        }

4.使用Redux-thunk中间件进行ajax请求发送

  • 可以利用Redux-thunk中间件将异步请求和复杂逻辑放到action中处理

  • Redux-thunk是redux的中间件

  • 利用Redux-thunk进行代码的编写

    • 安装Redux-thunk:yarn add redux-thunk

    • 使用Redux-thunk

      • store的index.js要在redux中引入applyMiddleware,从而保证使用中间件;在redux-thunk引入thunk模块
      //[store/index.js]
      import {applyMiddleware} from 'redux';
      import thunk from 'redux-thunk';
      • 在createStore的第二个参数使用thunk中间件
      //[store/index.js]
      const store = createStore(
      reducer,
      applyMiddleware(thunk),
      );

      thunk是redux的中间件

      • 若想同时使用Redux-devTools-Extension和thunk,则需要对store做如下配置
      import {createStore,applyMiddleware,compose} from 'redux'//引入compose
      import reducer from './reducer' //将笔记本reducer引入进来
      import thunk from 'redux-thunk';
      
      const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
          window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
      const enhancer = composeEnhancers(
        applyMiddleware(thunk),
      );
      //创建数据的公共存储仓库
      const store = createStore(
          reducer,
          enhancer
          );
      export default store;
      • 使用redux-thunk可以将返回值为函数的内容发送给store。store发现action是一个函数时,会自动执行这个函数。action的函数的第一个参数是dispatch方法,故可以在action的函数中创建action对象,并利用dispatch(action对象)将action对象发送给store。此时store得到的是action对象则会去调用reducer对数据进行处理。
      //[TodoList.js]使用redux-thunk可以将返回值为函数的内容发送给store
      import {getTodoList} from './store/actionCreators'
      componentDidMount(){
          const action=getTodoList();
          store.dispatch(action);//当调用store.dispatch将action发送给store时,action会被自动执行
      }
      //[store/actionCreators.js]store发现action是一个函数时,会自动执行这个函数。action的函数的第一个参数是dispatch方法,故可以在action的函数中创建action对象,并利用dispatch(action对象)将action对象发送给store。
      export const getTodoList= ()=>{
      return (dispatch)=>{
          axios.get("/list.json")
              .then((res)=>{
                  const data = res.data;
                  const action = initListAction(data);
                  dispatch(action);
              });
      }
      }
      //此时store得到的是action对象则会去调用reducer对数据进行处理。
      • 总结:借助redux-thunk,我们将异步请求放到actionCreators中进行管理。自动化测试时可以方便的去测试对应方法。

5.到底什么是Redux中间件?

  • Redux标准流程

    这里写图片描述

  • Redux中间件:是Action和Store的中间,对Dispatch方法的一个升级。如果Dispatch传递的是对象则直接传递给store;如果传递的是函数,则执行对应的函数。

6.Redux-saga中间件的使用

  • 安装Redux-saga:yarn add redux-saga

  • 运行流程

    • 在store中配置redux-saga

      • 引入createSagaMiddleware:import createSagaMiddleware from 'redux-saga';

      • 创建sagaMiddleware:const sagaMiddleware = createSagaMiddleware();

      • 通过applyMiddleware去使用中间件:applyMiddleware(sagaMiddleware);

      • 创建文件sagas.js

      • 在store中引入sagas.js并通过sagaMIddleware去运行这个文件:import todoSagas from './sagas'; sagaMiddleware.run(todoSagas);

      //[store/index.js]完整代码
      import {createStore,applyMiddleware,compose} from 'redux'
      import reducer from './reducer' //将笔记本引入进来
      import createSagaMiddleware from 'redux-saga';
      import todoSagas from './sagas';
      // create the saga middleware
      const sagaMiddleware = createSagaMiddleware()
      const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
          window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
      const enhancer = composeEnhancers(
          applyMiddleware(sagaMiddleware),
      );
      //创建数据的公共存储仓库
      const store = createStore(
          reducer,
          enhancer
          );
      sagaMiddleware.run(todoSagas);
      
      export default store;
    • 编写TodoList.js和actionCreators.js

      //TodoList.js
      componentDidMount(){
          const action = getInitList();
          store.dispatch(action);
      }
      //actionCreators.js
      export const getInitList = ()=>({
      type:GET_INIT_LIST
      })
    • 编写sagas.js

      • sagas需要导出一个generator函数,在函数中写相关逻辑【相关含义请参加注释】
      //存放异步逻辑
      import {takeEvery,put} from 'redux-saga/effects';//takeEvery是redux提供的方法
      import {GET_INIT_LIST} from './actionTypes';
      import {initListAction} from './actionCreators';
      import axios from 'axios';
      
      function* getInitList(){
      const res = yield axios.get("/list.json");//取数据
      const action = initListAction(res.data);//将数据的结果创建一个action
      yield put(action);//将action通过put方法派发给store——注意:put是redux-saga特有的派发函数。
      }
      
      //saga的导出必须是generator函数
      function* mySaga() {
      yield takeEvery(GET_INIT_LIST,getInitList);//表示捕捉到一个action类型为GET_INIT_LIST时,就会执行getInitList方法
      }
      
      export default mySaga;
    • store接收到sagas通过put过来的action,就转交给reducer进行判断,判断是GET_INIT_LIST类型的action会执行对应代码,从而返回新state,store更新state,页面重新渲染。

    • 总结:

      • redux-saga的使用是在组件编写完actionCreators会在store给reducer的同时,还给saga的导出的函数进行处理,在saga中通过takeEvery如果匹配上action类型则会执行对应函数。
      • Redux-saga适合大型项目,拥有大量API;Redux-thunk没有API,只是帮助我们在action中返回的不指是对象还可以是方法,更加方便。
      • redux中间件是action和store之间的中间件,是dispatcher方法的升级。

7.React-Redux的使用

  • 什么是React-Redux?

    • 是第三方模块,帮助在React中更方便的使用Redux
  • 安装React-Redux:yarn add react-redux

  • 重点:

    • Provider组件(又称:提供器)是react-redux第一个核心组件,可以帮助我们把store提供给Provider内部的每一个组件
    • 子组件内部通过connect方法与store做连接。connect(连接规则mapStateToProps,连接规则mapDispatcherToProps)(组件名)表示该组件要和store进行连接,把store里的数据和store里的数据的关系在mapStateToProps中列清楚;把组件里props如何对store里的数据做修改和store里的dispatch做关联,通过mapDispatchToProps函数返回的对象。
  • 示例代码[详情信息参见注释]:

    //[index.js]
    import React from 'react';
    import ReactDOM from 'react-dom';
    import TodoList from './TodoList';
    import {Provider} from 'react-redux';//Provider就是一个组件,提供器连接了store,则提供器内部的所有组件都有能力获取store的内容
    import store from './store';//引入store
    const App=(
        <Provider store={store}>
            <TodoList/>
        </Provider>
    );
    ReactDOM.render(App, document.getElementById('root'));
    
    //[TodoList.js]
    import React,{Component} from 'react';
    import {connect} from 'react-redux';//使组件和store连接
    class TodoList extends Component{
        render(){
            return(
                <div>
                    <div>
                        {/*this.props.inputValue根据mapStateToProps方法获得的值;this.props.changeInputValue通过mapDispatchToProps方法获得的函数*/}
                        <input value={this.props.inputValue} onChange={this.props.changeInputValue}/>
                        <button>提交</button>
                    </div>
                    <ul>
                        <li>Dell</li>
                        <li>jack</li>
                    </ul>
                </div>
            )
        }
    
    }
    
    //mapStateToProps表示怎么映射?将Store中的数据映射给组件成为Props值,state参数表示store中的数据
    const mapStateToProps = (state)=>{
        return{
            inputValue:state.inputValue
        }
    }
    
    //mapDispatchToProps表示将store的dispatch方法挂载到props上,通过this.props来调用dispatch方法
    const mapDispatchToProps=(dispatch)=>{
        return{
            changeInputValue(e){
                const action = {
                    type:'change_input_value',
                    value:e.target.value
                }
                dispatch(action);
            }
        }
    }
    export default connect(mapStateToProps,mapDispatchToProps)(TodoList);//让TodoList组件和store做连接,store中的数据会映射到props中,同时如果想对store做修改,可以通过connect第二个参数将dispatch方法映射到props上
    
    //[store/index.js]
    import {createStore} from 'redux';
    import reducer from './reducer';
    const store = createStore(reducer);
    export default store;
    
    //[store/reducer.js]
    const defaultState = {
        inputValue:'hello world',
        list:[]
    }
    export default (state=defaultState,action)=>{
        if(action.type === "change_input_value"){
            const newState = JSON.parse(JSON.stringify(state));//对state进行深拷贝
            newState.inputValue = action.value;
            return newState;
        }
        return state;
    }

8.React-Redux的使用

  • 示例代码[详情请参见注释]:

    //[index.js]
    import React from 'react';
    import ReactDOM from 'react-dom';
    import TodoList from './TodoList';
    import {Provider} from 'react-redux';//Provider就是一个组件,提供器连接了store,则提供器内部的所有组件都有能力获取store的内容
    import store from './store';//引入store
    const App=(
        <Provider store={store}>
            <TodoList/>
        </Provider>
    );
    ReactDOM.render(App, document.getElementById('root'));
    
    //[TodoList.js]
    import React from 'react';
    import {connect} from 'react-redux';//使组件和store连接
    //由于TodoList组件只进行显示而没有业务逻辑,且只有render方法,故既是UI组件又是无状态组件
    const TodoList = (props)=>{
        const {inputValue,list,changeInputValue,handleClick} = props;//通过解构赋值定义变量
        return(
            <div>
                <div>
                    {/*this.props.inputValue根据mapStateToProps方法获得的;this.props.changeInputValue通过mapDispatchToProps方法获得*/}
                    <input value={inputValue} onChange={changeInputValue}/>
                    <button onClick={handleClick}>提交</button>
                </div>
                <ul>
                    {
                        list.map((item,index)=>{
                            return(
                                <li key={index} onClick={()=>{props.handleDelete(index)}}>{item}</li>
                            )
                        })
                    }
                </ul>
            </div>
        )
    }
    //mapStateToProps表示怎么映射?将Store中的数据映射给组件成为Props值,state参数表示store中的数据
    const mapStateToProps = (state)=>{
        return{
            inputValue:state.inputValue,
            list:state.list
        }
    }
    //mapDispatchToProps表示将store的dispatch方法挂载到props上,通过this.props来调用dispatch方法
    const mapDispatchToProps=(dispatch)=>{
        return{
            changeInputValue(e){
                const action = {
                    type:'change_input_value',
                    value:e.target.value
                }
                dispatch(action);
            },
            handleClick(){
                const action={
                    type:'add_item'
                }
                dispatch(action);
            },
            handleDelete(index){
                const action={
                    type:'delete_item',
                    index:index
                };
                dispatch(action);
            }
        }
    }
    //理解connect:TodoList本身是UI组件,只做显示。但是connect方法将连接规则和组件相结合,返回的结果是一个容器组件(有业务逻辑和UI)。
    export default connect(mapStateToProps,mapDispatchToProps)(TodoList);//让TodoList组件和store做连接,store中的数据会映射到props中,同时如果想对store做修改,可以通过connect第二个参数将dispatch方法映射到props上
    
    //[store/index.js]
    import {createStore} from 'redux';
    import reducer from './reducer';
    const store = createStore(reducer);
    export default store;
    
    //[store/reducer.js]
    const defaultState = {
        inputValue:'',
        list:[]
    }
    
    export default (state=defaultState,action)=>{
        if(action.type === "change_input_value"){
            const newState = JSON.parse(JSON.stringify(state));//对state进行深拷贝
            newState.inputValue = action.value;
            return newState;
        }else if(action.type === "add_item"){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.push(newState.inputValue);
            newState.inputValue='';
            return newState;
        }else if(action.type === "delete_item"){
            const newState = JSON.parse(JSON.stringify(state));
            newState.list.splice(action.index,1);
            return newState;
        }
        return state;
    }
  • 注意点:

    • 理解connect:TodoList本身是UI组件,只做显示,故可以转换成无状态组件从而提升性能。但是connect方法将连接规则和组件相结合,返回的结果是一个容器组件(有业务逻辑和UI)。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值