Redux, Redux-saga,React-redux,Immer,Reselect,State范式化

1.概念

1-1.整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中

1-2.唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象

1-3.为了描述 action 如何改变 state tree ,你需要编写 reducers(纯函数)

 

2. action

export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO ';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
export const VisibilityFilters = {
    SHOW_ALL: 'SHOW_ALL',
    SHOW_COMPLETED: 'SHOW_COMPLETED',
    SHOW_ACTIVE: 'SHOW_ACTIVE'
}
//上面部分只是单独的type,可以用专门的js统一保存

//action说白了就是一种命令,返回一个json,type是命令的类型,其余的key是要用到的参数
//action是用dispatch触发的,然后在reducer里面处理数据

let nextTodoId = 0

export const addTodo = text => ({
    type: ADD_TODO,
    id: nextTodoId++,
    text
})

export const setVisibilityFilter = filter => ({
    type: SET_VISIBILITY_FILTER,
    filter
})

export const toggleTodo = id => ({
    type: TOGGLE_TODO,
    id
})



3.reducer    

不可变数据也可以使用专门的库  直通车: Immer

//reducer尽量是功能要单一,第一个参数是初始状态,每个功能的状态的类型都可能不一样,初始状态是undefined,这里用es6初始值赋值
//比如改变TODO列表的reducer就是个数组,过滤列表数据的就是String
//reducer必须是纯函数,传入state,返回也是state,不可直接操作数据,产生副作用
//下面例子就是用的...来处理的,也可以用Object.assign进行浅复制

import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './action'

import { combineReducers } from 'redux'

const { SHOW_ALL } = VisibilityFilters;

function visibilityFilter(state = SHOW_ALL, action) {
    switch (action.type) {
        case SET_VISIBILITY_FILTER:
            return action.filter;
        default: return state;
    }
}


function todos(state = [], action) {
    switch (action.type) {
        case ADD_TODO:
            return [
                ...state,
                {
                    id: action.id,
                    text: action.text,
                    completed: false
                }
            ]
        case TOGGLE_TODO:
            let list = state.map((item) => {
                if (item.id === action.id) {
                    return {
                        ...item,
                        completed: !item.completed
                    }
                } else {
                    return item
                }
            });
            return list;
        default:
            return state;
    }
}

//这是引用了redux-saga进行异步处理

function sogaMsg(state = {}, action) {
    switch (action.type) {
        //这边使用的是字符串,最好是用上面的方式,进行action type的统一管理
        case "TEST_SUCCEEDED":
            return action.data;
        default: return state;
    }
}

//这是引用了redux-saga进行异步处理

function lanMsg(state = {}, action) {
    switch (action.type) {
        //这边使用的是字符串,最好是用上面的方式,进行action type的统一管理
        case "LAN_SUCCEEDED":
            return action.data;
        default: return state;
    }
}

//组合reducer,类似于json的key-value的形式,key就是state里面的key字段,位于顶层
//这个reducer的js是直接用于定义reducers的,位于顶层
//如果引入一个combineReducers的js,当作这个combineReducers的里面的key-value,就会顶层下出现嵌套
//下面的几个都是顶层的,没有嵌套
const toDoApp = combineReducers({ visibilityFilter, todoList: todos, sogaMsg,lanMsg });

export default toDoApp

4.Saga.js   具体API可以去这里了解   Redux-saga

import { takeEvery, put, all, call,fork } from 'redux-saga/effects'

//这是axios的请求
import { testSaga,testLanguage } from '../components/Api'

function* testInnerSaga() {
    const data = yield call(testSaga);
    yield put({ type: "TEST_SUCCEEDED" ,data:data.data.data}); //dispatch action
}

function* watchTestInnerSaga() {
     //监听TEST_REQUEST的action,触发就执行testInnerSaga
    yield takeEvery("TEST_REQUEST", testInnerSaga);
}


function* testInnerLan() {
    
    const data = yield call(testLanguage);
    yield put({ type: "LAN_SUCCEEDED" ,data:data.data.data});  //dispatch action
}

function* watchTestInnerLan() {
    //监听LAN_REQUEST的action,触发就执行testInnerLan
    yield takeEvery("LAN_REQUEST", testInnerLan);
}

export default function* rootSaga() {
    //多个同时启动
    yield all([
        fork(watchTestInnerSaga),
        fork(watchTestInnerLan)
    ])
}

5.Api.js

import Axios from 'axios'
Axios.defaults.baseURL = 'http://172.16.3.25/mantis';

export const testSaga = () => {
    return Axios.get('/systemInit/loadInitStatus');
}

export const testLanguage=()=>{
    return Axios.get("/api/coreresource/i18n/getLangItems/v1?languageCode=zh_CN")
}

6.入口进行注册

import React, { Component, Suspense, lazy } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import { createStore, applyMiddleware } from 'redux' //加入中间件applyMiddleware
import toDoApp from './redux/reducer'  // reducers
import { Provider } from 'react-redux'  //提供redux的store,用于组件的穿透
import createSagaMiddleware from 'redux-saga' //引入redux-saga
import MySoga from './components/Saga' //soga的js

//这是react16新的懒加载
const App = lazy(() => import('./App'))
const Test = lazy(() => import('./components/Test'))

//redux soga配置
const sagaMiddleware = createSagaMiddleware()
const store = createStore(toDoApp, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(MySoga)

class Routers extends Component {
    render() {
        return (

            <Provider store={store}>
                <Suspense fallback={<div>loading</div>}>
                    <BrowserRouter>
                        <Switch>
                            <Route exact path="/" component={App}></Route>
                            <Route exact path="/test" component={Test}></Route>
                            <Route component={NoMatch}></Route>
                        </Switch>
                    </BrowserRouter>
                </Suspense>
            </Provider>

        )
    }
}

function NoMatch({ location }) {
    return (
        <h3>没有匹配到当前的路由:{location.pathname}</h3>
    )
}

export default Routers

7.mapStateToProps,mapDispatchToProps(字面意思,store里面的state转化成props,dispatch的函数转化成props【事件】)

直通车: Reselect

VisibleTodoList.js:

//这是react-redux的语法

import { toggleTodo } from '../redux/action' //这是action
import TodoList from './TodoList' //TODOList组件
import { connect } from 'react-redux'

//这是处理数据的函数
//每当组件更新时都会重新计算 todos。
//如果 state tree 非常大,或者计算量非常大,每次更新都重新计算可能会带来性能问题
//mapStateToProps 如果要提升性能,可以考虑 reselect
const getVisibleTodos = (todoList, filter) => {
    switch (filter) {
        case 'SHOW_COMPLETED':
            return todoList.filter(t => t.completed)
        case 'SHOW_ACTIVE':
            return todoList.filter(t => !t.completed)
        case 'SHOW_ALL':
        default:
            return todoList
    }
}

//返回的是json,key就是传入组件的属性
const mapStateToProps = state => ({
    todoList: getVisibleTodos(state.todoList, state.visibilityFilter)
})

//返回的是json,key就是传入组件的事件
const mapDispatchToProps = dispatch => ({
    toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(mapStateToProps,mapDispatchToProps)(TodoList)  //进行连接
//TodoList.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

export class TodoList extends Component {
    render() {
        const { todoList, toggleTodo } = this.props;
        return (
            <ul>
                {
                    todoList.map((item, index) => {
                        return <Todo key={index} {...item} onClick={() => toggleTodo(index)} />
                    })
                }
            </ul>
        )
    }
}

TodoList.propTypes = {
    toggleTodo: PropTypes.func.isRequired,
    todos: PropTypes.arrayOf(
        PropTypes.shape({
            id: PropTypes.number.isRequired,
            completed: PropTypes.bool.isRequired,
            text: PropTypes.string.isRequired
        })
    )
}

export default TodoList

一般推荐上面两个分开,如果你的逻辑不复杂,可以将这两个js合并,如下:

//InfoWrap.js
//这个就是redux-saga的应用组件

import { connect } from 'react-redux'
import React, { Component } from 'react'
const mapStateToProps = (state) => ({
    sogaMsg: state.sogaMsg,
    lanMsg: state.lanMsg,
    allMsg: state
})

const mapDispatchToProps = (dispatch) => ({
    onClick: () => {
        dispatch({ type: 'LAN_REQUEST' })
        dispatch({ type: 'TEST_REQUEST' });
        
    }
})

class Info extends Component {
    render() {
        const { sogaMsg, onClick, lanMsg,allMsg } = this.props;
        return (
            <div>
                {<p style={{borderBottom:'1px solid #999'}}>{sogaMsg.lastMapConfigFile}</p>}
                {
                    Object.keys(lanMsg).map((lan) => {
                        return <p key={lan}>{lanMsg[lan]}</p>
                    })
                }

                <button onClick={onClick}>获取异步数据</button>
            </div>
        )
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(Info)

8.其他组件

//Todo.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export class Todo extends Component {
    render() {
        const { onClick, completed, text } = this.props;
        return (
            <li onClick={onClick} style={{
                textDecoration: completed ? 'line-through' : 'none'
            }}>{text}</li>
        )
    }
}

Todo.propTypes = {
    onClick: PropTypes.func.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
}

export default Todo
//Link.js

import React from 'react'
import PropTypes from 'prop-types'

const Link = ({ active, children, onClick }) => (
  <button
    onClick={onClick}
    disabled={active}
    style={{
      marginLeft: '4px'
    }}
  >
    {children}
  </button>
)

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link
// FilterLink.js

import Link from './Link'
import { connect } from 'react-redux'

import { setVisibilityFilter } from '../redux/action'

const mapStateToProps = (state, ownProps) => ({
    active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
    onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

export default connect(mapStateToProps,mapDispatchToProps)(Link)
// Foot.js

//函数的模式

import React from 'react'
import FilterLink from './FilterLink'
import { VisibilityFilters } from '../redux/action'

const Footer = () => (
  <div>
    <span>Show: </span>
    <FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
    <FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
    <FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
  </div>
)

export default Footer
//AddTodo.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../redux/action'

export class AddTodo extends Component {
    subMsg(name,e) {
        const {dispatch} =this.props;
        e.preventDefault();
        if (!this.input.value.trim()) return;

        dispatch(addTodo(`${name}----${this.input.value}`));
        this.input.value = '';

    }
    render() {
        return (
            <form onSubmit={this.subMsg.bind(this,"test")}>
                <input ref={(node) => this.input = node} />
                <button type="submit">Add Todo</button>
            </form>
        )
    }
}

export default connect()(AddTodo)
// App.js

import React, { Component } from 'react';
import './app.css'
import AddTodo from './components/AddTodo'
import VisibleTodoList from './components/VisibleTodoList'
import InfoWrap from './components/InfoWrap'
import Foot from './components/Foot'

class App extends Component {
  constructor(props) {
    super(props);
    console.log(props)
  }
  render() {
    return (
      <div className="wrap">
        <AddTodo />
        <VisibleTodoList />
        <Foot />
        <InfoWrap/>
      </div>
    )
  }
}


export default App;

9. 效果如下:



点击当前项,出现删除线


筛选:Active,上面默认是All


 筛选:Completed


点击按钮,异步获取数据: 

 


10.State范式化  可以使用库  Normalizr

redux的state数据复杂会出现如下问题:

(1) 当数据在多处冗余后,需要更新时,很难保证所有的数据都进行更新

(2) 嵌套的数据意味着 reducer 逻辑嵌套更多、复杂度更高。尤其是在打算更新深层嵌套数据时

(3)  不可变的数据在更新时需要状态树的祖先数据进行复制和更新,并且新的对象引用会导致与之 connect 的所有 UI 组件都重复 render。尽管要显示的数据没有发生任何改变,对深层嵌套的数据对象进行更新也会强制完全无关的 UI 组件重复 render

所以,redux Store 中管理关系数据或嵌套数据的推荐做法是将这一部分视为数据库,进行表的分割,用id进行连接,并且将数据按范式化存储

范式化的数据包含下面几个概念:

  • 任何类型的数据在 state 中都有自己的 “表”。
  • 任何 “数据表” 应将各个项目存储在对象中,其中每个项目的 ID 作为 key,项目本身作为 value。
  • 任何对单个项目的引用都应该根据存储项目的 ID 来完成。
  • ID 数组应该用于排序。
//这是一个博客的json数组

//进行表的拆分就是博客表,评论表,用户表

const blogPosts = [
    {
        id : "post1",
        author : {username : "user1", name : "User 1"},
        body : "......",
        comments : [
            {
                id : "comment1",
                author : {username : "user2", name : "User 2"},
                comment : ".....",
            },
            {
                id : "comment2",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    },
    {
        id : "post2",
        author : {username : "user2", name : "User 2"},
        body : "......",
        comments : [
            {
                id : "comment3",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            },
            {
                id : "comment4",
                author : {username : "user1", name : "User 1"},
                comment : ".....",
            },
            {
                id : "comment5",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    }
    // and repeat many times
]

 范式化之后:

//通过id进行连接
//id前后就是排序

{
    posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]    
            },
            "post2" : {
                id : "post2",
                author : "user2",
                body : "......",
                comments : ["comment3", "comment4", "comment5"]    
            }
        }
        allIds : ["post1", "post2"]
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                author : "user2",
                comment : ".....",
            },
            "comment2" : {
                id : "comment2",
                author : "user3",
                comment : ".....",
            },
            "comment3" : {
                id : "comment3",
                author : "user3",
                comment : ".....",
            },
            "comment4" : {
                id : "comment4",
                author : "user1",
                comment : ".....",
            },
            "comment5" : {
                id : "comment5",
                author : "user3",
                comment : ".....",
            },
        },
        allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"]
    },
    users : {
        byId : {
            "user1" : {
                username : "user1",
                name : "User 1",
            }
            "user2" : {
                username : "user2",
                name : "User 2",
            }
            "user3" : {
                username : "user3",
                name : "User 3",
            }
        },
        allIds : ["user1", "user2", "user3"]
    }
}

11.范式化数据的管理和使用  可以使用库  redux-orm

当然也可以自己逻辑,就是操作表的id

// actions.js
function addComment(postId, commentText) {
    // 为这个 comment 生成一个独一无二的 ID
    const commentId = generateId("comment");

    return {
        type : "ADD_COMMENT",
        payload : {
            postId,
            commentId,
            commentText
        }
    };
}


// reducers/posts.js
function addComment(state, action) {
    const {payload} = action;
    const {postId, commentId} = payload;

    // 查找出相应的文章,简化其余代码
    const post = state[postId];

    return {
        ...state,
        // 用新的 comments 数据更新 Post 对象
        [postId] : {
             ...post,
             comments : post.comments.concat(commentId)             
        }
    };
}

function postsById(state = {}, action) {
    switch(action.type) {
        case "ADD_COMMENT" : return addComment(state, action);
        default : return state;
    }
}

function allPosts(state = [], action) {
    // 省略,这个例子中不需要它
}

const postsReducer = combineReducers({
    byId : postsById,
    allIds : allPosts
});


// reducers/comments.js
function addCommentEntry(state, action) {
    const {payload} = action;
    const {commentId, commentText} = payload;

    // 创建一个新的 Comment 对象
    const comment = {id : commentId, text : commentText};

    // 在查询表中插入新的 Comment 对象
    return {
        ...state,
        [commentId] : comment
    };
}

function commentsById(state = {}, action) {
    switch(action.type) {
        case "ADD_COMMENT" : return addCommentEntry(state, action);
        default : return state;
    }
}


function addCommentId(state, action) {
    const {payload} = action;
    const {commentId} = payload;
    // 把新 Comment 的 ID 添加在 all IDs 的列表后面
    return state.concat(commentId);
}

function allComments(state = [], action) {
    switch(action.type) {
        case "ADD_COMMENT" : return addCommentId(state, action);
        default : return state;
    }
}

const commentsReducer = combineReducers({
    byId : commentsById,
    allIds : allComments
});

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值