zoukankan      html  css  js  c++  java
  • Redux 一步到位

    简介

    • Redux 是 JavaScript 状态容器,提供可预测化的状态管理
    • Redux 除了和 React 一起用外,还支持其它库( jquery ... )
    • 它体小精悍(只有2kB,包括依赖)
    • 由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性。

    安装

    • 稳定版 npm install --save redux
    • 附加包 React 绑定库 npm install --save react-redux
    • 附加包 开发者工具 npm install --save-dev redux-devtools

    创建 reducer.js

    应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,先编写 reducers。

    const defaultState = {}
    export default (state = defaultState,action)=>{
        return state
    }
    

    创建 Store

    import { createStore } from 'redux'
    import reducer from './reducer'
    const store = createStore(reducer)
    export default store
    
    • 使用 createStore 创建数据储存仓库
    • 将 store 暴露出去

    获取 store

    组件来获取 store 中的数据

    import store from './store'
    // ...
    constructor(props){
        super(props)
        console.log(store.getState())
    }
    
    • 先引入store
    • 使用 getState 函数获取数据

    安装 Redux DevTools

    chrome 搜索插件 Redux DevTools 并安装

     import { createStore } from 'redux'
     import reducer from './reducer'
     const store = createStore(reducer,
    + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
     export default store
    
    • 只是添加了一句话
    • 意思是看window里有没有这个方法,有则执行这个方法
    • 启动项目就可以看到 State 了

    Action

    Action 是 store 数据的唯一来源。

    创建 action

    const action ={
            type:'',
            value: ''
        }
    
    store.dispatch(action)
    
    • type 字段来表示将要执行的动作(必须要有)
    • 除了 type 字段外,action 对象的结构完全自由
    • 使用 dispatch 函数发送数据到 store

    更改 Reducer

    export default (state = defaultState,action)=>{
        if(action.type === 'changeInput'){
            let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
            newState.inputValue = action.value
            return newState
        }
        return state
    }
    
    • 先判断type是否正确,如果正确,声明一个变量newState
    • Reducer 里只能接收 state,不能改变 state,所以将新变量 return

    更新组件数据

    constructor(props){
        // ...
        storeChange(){
         this.setState(store.getState())
        }
        this.storeChange = this.storeChange.bind(this)
        store.subscribe(this.storeChange)
    }
    
    • bing(this) 转变this指向
    • storeChange 重新setState
    • subscribe 函数用来订阅 store 状态

    小技巧

    抽离 Action Types

    使用单独的模块或文件来定义 action type 常量并不是必须的,甚至根本不需要定义。对于小应用来说,使用字符串做 action type 更方便些。不过,在大型应用中把它们显式地定义成常量还是利大于弊的。

    actionTypes.js

    const ADD_TODO = 'ADD_TODO';
    const REMOVE_TODO = 'REMOVE_TODO';
    const LOAD_ARTICLE = 'LOAD_ARTICLE';
    

    组件中引用

    import { ADD_TODO , REMOVE_TODO , LOAD_ARTICLE } from './store/actionTypes'
    

    相应的 Reducer 也要更改

    import { ADD_TODO , REMOVE_TODO , LOAD_ARTICLE } from './store/actionTypes'
    
    const defaultState = {}
    export default (state = defaultState,action)=>{
        if(action.type === ADD_TODO){
            let newState = JSON.parse(JSON.stringify(state))
            newState.inputValue = action.value
            return newState
        }
        // ...
        return state
    }
    

    抽离 Redux Action

    Action 创建函数 就是生成 action 的方法。注意与 action 概念相区分。

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

    actionCreators.js

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

    组件中使用

    import { addTodo } from './actionCreators';
    
    // ...
    dispatch(addTodo('Use Redux'))
    

    注意

    • store 必须是唯一的
    • 只有store能改变自己的内容,Reducer不能改变
    • Reducer必须是纯函数

    拆分组件UI和业务逻辑

    TodoListUI.js

    import React, { Component } from 'react';
    class TodoListUi extends Component {
    
        render() {
            return ( <div>123</div> );
        }
    }
    
    export default TodoListUi;
    

    TodoList.js

    import TodoListUI from './TodoListUI'
    
    render() {
        return (
            <TodoListUI />
        );
    }
    
    • constructor 中对于对应方法要重新绑定 this
    • 修改完 TodoList.js 文件,还要对UI组件进行对应的属性替换

    无状态组件

    • 无状态组件其实就是一个函数
    • 不用继承任何的 class
    • 不存在 state
    • 因为无状态组件其实就是一个函数, 性能比普通的React组件好

    TodoListUi 改写成无状态组件

    import React from 'react';
    
    const TodoListUi = (props)=>{
        return(
            <> some code </>
        )
    }
    
    export default TodoListUi;
    

    Axios 异步获取数据和 Redux 结合

    不过就是走一遍上面的流程

    actionCreatores.js

    export const getListAction  = (data)=>({
        type: xxx,
        data
    })
    

    组件

    import axios from 'axios'
    import {getListAction} from './store/actionCreatores'
    
    componentDidMount(){
        axios.get('https:// xxx').then((res)=>{
            const data = res.data
            const action = getListAction(data)
            store.dispatch(action)
        })
    }
    

    reducer.js

    import {GET_LIST} from './actionTypes'
    
    const defaultState = {
        list:[]
    }
    export default (state = defaultState,action)=>{
        if(action.type === GET_LIST ){
            let newState = JSON.parse(JSON.stringify(state))
            newState.list = action.data.data.list
            return newState
        }
    
        return state
    }
    

    Redux 中间件

    注意不是 react 中间件

    Redux-thunk

    • Redux-thunk
    • Redux-thunk 是对 Redux 中 dispatch 的加强
    npm install --save redux-thunk
    
    import { createStore , applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    
    const store = createStore(
        reducer,
        applyMiddleware(thunk)
    )
    
    • 使用中间件需要先引入 applyMiddleware
    • 可以这样 但是我们使用 Dev Tool 占用了第二个参数

    所以我们这样写

    import { createStore , applyMiddleware ,compose } from 'redux'
    
    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
    
    • 利用compose创造一个增强函数 composeEnhancers,就相当于建立了一个链式函数
    • 把thunk加入 ( applyMiddleware(thunk) )
    • 直接在createStore函数中的第二个参数,使用这个 enhancer 变量

    在 actionCreators.js 中写业务

    actionCreators.js 都是定义好的 action,根本没办法写业务逻辑,有了Redux-thunk之后,可以把TodoList.js中的 componentDidMount 业务逻辑放到这里来编写。

    import axios from 'axios'
    
    //...
    export const getTodoList = () =>{
        return (dispatch)=>{
            axios.get('https:// xxx ').then((res)=>{
                const data = res.data
                const action = getListAction(data)
                dispatch(action)
            })
        }
    }
    

    以前的action是对象,现在的action可以是函数了,这就是redux-thunk带来的好处

    组件中

    import { getTodoList } from './store/actionCreatores'
    // ...
    componentDidMount(){
        const action = getTodoList()
        store.dispatch(action)
    }
    

    Redu-saga

    安装

    npm install --save redux-saga
    

    store/index.js

    import createSagaMiddleware from 'redux-saga'
    const sagaMiddleware = createSagaMiddleware();
    
    • 引入saga
    • 创建saga中间件

    Redux-thunk 替换成 saga

    import { createStore , applyMiddleware ,compose } from 'redux'
    import reducer from './reducer'
    import createSagaMiddleware from 'redux-saga'
    
    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)
    export default store
    

    创建 store/sagas.js

    import {takeEvery, put} from 'redux-saga/effects'
    import {GET_MY_LIST} from './actionTypes'
    import {getListAction} from './actionCreatores'
    import axios from 'axios'
    
    //generator函数
    function* mySaga() {
        //等待捕获action
        yield takeEvery(GET_MY_LIST, getList)
    }
    
    function* getList(){
        const res = yield axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList')
        const action = getListAction(res.data)
        yield put(action)
    }
    
    export default mySaga;
    

    store/index.js

    import { createStore , applyMiddleware ,compose } from 'redux'  //  引入createStore方法
    import reducer from './reducer'
    import createSagaMiddleware from 'redux-saga'
    import mySagas from './sagas'
    
    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(mySagas)
    
    export default store
    

    react-redux

    react-redux 不是 redux,
    React-Redux 是 Redux 的官方 React 绑定库。它能够使你的 React 组件从 Redux store 中读取数据,并且向 store 分发 actions 以更新数据

    npm install --save react-redux
    

    是一个提供器,只要使用了这个组件,组件里边的其它所有组件都可以使用store了

    import React from 'react';
    import ReactDOM from 'react-dom';
    import TodoList from './TodoList'
    import { Provider } from 'react-redux'
    import store from './store'
    const App = (
        <Provider store={store}>
            <TodoList />
        </Provider>
    )
    ReactDOM.render(App, document.getElementById('root'));
    

    connect 连接器

    • connect 可用来获取 store 中的数据
    • connect 的作用是把UI组件(无状态组件)和业务逻辑代码的分开,然后通过connect再链接到一起,让代码更加清晰和易于维护。

    先制作映射关系,映射关系就是把原来的state映射成组件中的props属性

    const stateToProps = (state)=>{
        return {
                inputValue: state.inputValue
        }
    }
    

    使用 connect 获取 store 中的数据

    import {connect} from 'react-redux'
    export default connect(inputValue, null)(TodoList); // 这里的 inputValue 代表一个映射关系
    

    修改 store 中的数据

    例子:当我们修改中的值时,去改变store数据,UI界面也随之进行改变。

    import React, { Component } from 'react';
    import store from './store'
    import { connect } from 'react-redux'
    
    class TodoList extends Component {
        constructor(props){
            super(props)
            this.state = store.getState()
        }
        render() {
            return (
                <div>
                    <div>
                        <input value={this.props.inputValue} onChange={this.props.inputChange} />
                        <button>提交</button>
                    </div>
                    <ul>
                        <li></li>
                    </ul>
                </div>
                );
        }
    }
    const stateToProps = (state)=>{
        return {
            inputValue : state.inputValue
        }
    }
    
    const dispatchToProps = (dispatch) =>{
        return {
            inputChange(e){
                console.log(e.target.value)
            }
        }
    }
    
    export default connect(stateToProps,dispatchToProps)(TodoList);
    

    派发 action 到 store 中 (再走一遍流程)

    const dispatchToProps = (dispatch) =>{
        return {
            inputChange(e){
                let action = {
                    type:'change_input',
                    value:e.target.value
                }
                dispatch(action)
            }
        }
    }
    

    reducer

    const defalutState = {
        inputValue : 'jspang',
        list :[]
    }
    export default (state = defalutState,action) =>{
        if(action.type === 'change_input'){
            let newState = JSON.parse(JSON.stringify(state))
            newState.inputValue = action.value
            return newState
        }
        return state
    }
    

    参考资料

    • 哔哩哔哩 jspang 的 视频
    • 相关官方文档
  • 相关阅读:
    图片上下左右居中
    点击滚动指定高度 屏幕滚动事件
    实例16 验证登录信息的合法性
    实例15 判断某一年是否为闰年
    实例14 实现两个变量的互换(不借助第3个变量)
    实例13 不用乘法运算符实现2*16
    实例12 用三元运算符判断奇数和偶数
    实例11 加密可以这样简单(位运算)
    实例10 自动类型转换与强制类型转换
    实例9 重定向输入流实现程序日志
  • 原文地址:https://www.cnblogs.com/guangzan/p/12215069.html
Copyright © 2011-2022 走看看