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
});