Redux 是一个 JavaScript 应用状态管理的库,当项目很复杂的时候,属性传递已经达不到我们预期,可以使用Redux 解决数据传递问题,统一状态管理。换句话说,Redux就是用来处理和管理应用的状态/数据。
Redux设计思想
Redux是将整个应用状态存储到到一个地方,称为store
里面保存一棵状态树(state tree)
组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
其它组件可以通过订阅store中的状态(state)来刷新自己的视图
Redux概念解析
Store
Redux的核心是一个store
,就是保存数据的地方,可以看出是一个容器,整个应用就只能有一个store。Redux提供createStore()
函数来生成store。
import { createStore } from 'redux';let store = createStore(fn);复制代码
上面代码中,createStore函数接受另一个函数作为参数,返回新生成的Store对象。
State
store 某个节点对应的数据集合就是state。state
是被托管的数据,也就是每次触发监听事件,我们要操作的数据。可以通过store.getState()
获得。
Redux 规定,一个state对应一个View。State相同,则View相同。
let store = createStore(fn);let state = store.getState();复制代码
Action
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action 是一个对象。其中的type
属性是必须的,表示 Action 的名称。其他属性可以自由设置。
{ type: types.ADD_TODO , text: '读书' }复制代码
Actor Creator
action creator 顾名思义就是用来创建 action 的,action creator 只简单的返回 action。
const ADD_TODO = 'ADD_TODO';let actions = { addTodo(todo){ return { type: ADD_TODO, todo} }}复制代码
store.dispath(action)
store.dispatch()是 View 发出 Action 的唯一方法。store.dispatch接受一个 Action 对象作为参数,将它发送出去
let store=createStore(reducer);store.dispatch({ type:'ADD_TODO',text:'读书'});复制代码
结合 Action Creator,这段代码可以改写如下。
store.dispatch(actions.addTodo(e.target.value))复制代码
Redux 核心API
createStore
使用方法
let store=createStore(reducer);
reducer
在Redux中,负责响应
action
并修改数据的角色就是reducer
。 reducer要有两个参数 ,要根据老的状态和新传递的动作算出新的状态。reducer
本质上是一个纯函数,每次需要返回一个新的状态对象。//以下为reducer的格式const todo = (state = initialState, action) => { switch(action.type) { case 'XXX': return //具体的业务逻辑; case 'XXX': return //具体的业务逻辑; default: return state; }}复制代码
getState()
将状态放到了容器中外部无法在进行更改了,使用获取
store
中的状态。dispatch(action)
subscribe(listener)
combineReducers
合并reducer,把他们合并成一个
key是新状态的命名空间,值是reducer,执行后会返回一个新的reducer。
let reducer = combineReducers({ c: counter, t: todo});复制代码
context
react提供一个context API,可以解决跨组件的数据传递。16.3版本以前的context和现在最新版context用法有区别。在16.3官方不推荐使用,如果某个组件shouldComponentUpdate返回了false后面的组件就不会更新了
contextAPI 新的方法非常简便。
import React from 'react';import {render} from 'react-dom';// 创建一个上下文,有两个属性 一个叫Provider 还有个叫Consume// createContext中的对象是默认参数let { Consumer,Provider} = React.createContext();// context 可以创建多个 这时候就不要解构了,不同的context是不能交互的class Title extends React.Component{ render(){ // 子类通过Consumer进行消费 内部必须是一个函数 函数的参数是Provider的value属性 return{({s,h})=>{ return }}class Head extends React.Component{ render() { return{ h('red'); }}>hello}}}}// Provider使用在父组件上class App extends React.Component{ constructor(){ super(); this.state = {color:'green'} } handleClick = (newColor) =>{ this.setState({ color: newColor}) } render(){ return}}render( ,window.root)复制代码
实现简单的Redux
了解基本概念后就来写一个实现简单功能的"redux"吧!实现把内容渲染到页面上
1.渲染状态
//数据源let appState={ title: {color: 'red',text: '标题'}, content:{color:'green',text:'内容'}}// 渲染标题function renderTitle(title) { let titleEle=document.querySelector('#title'); titleEle.innerHTML=title.text; titleEle.style.color=title.color;}// 渲染内容function renderContent(content) { let contentEle=document.querySelector('#content'); contentEle.innerHTML=content.text; contentEle.style.color=content.color;}// 执行渲染的方法function render(appState) { renderTitle(appState.title); renderContent(appState.content);}render(appState);复制代码
2.提高数据修改的门槛
状态不应该是全局的,也不应该哪个方法里直接可以更改(操作危险)
一旦数据可以任意修改,所有对共享状态的操作都是不可预料的
模块之间需要共享数据和数据可能被任意修改导致不可预料的结果之间有矛盾
所以提供一个修改状态的
dispatch
方法,不要去直接更改状态,对数据的操作修改必须通过这个方法
let appState={ title: {color: 'red',text: '标题'}, content:{color:'green',text:'内容'}}function renderTitle(title) { let titleEle=document.querySelector('#title'); titleEle.innerHTML=title.text; titleEle.style.color=title.color;}function renderContent(content) { let contentEle=document.querySelector('#content'); contentEle.innerHTML=content.text; contentEle.style.color=content.color;}function render(appState) { renderTitle(appState.title); renderContent(appState.content);}//先定义好要做那些事情(常量) 也叫宏const UPDATE_TITLE_COLOR = 'UPDATE_TITLE_COLOR';const UPDATE_CONTENT_CONTENT = 'UPDATE_CONTENT_CONTENT';// 派发的方法,用来更改状态// 派发时应该将修改的动作action提交过来,是个对象,对象里的type属性是固定必须的。function dispatch(action) { switch (action.type) { case UPDATE_TITLE_COLOR: appState.title.color=action.color; break; case UPDATE_CONTENT_CONTENT: appState.content.text=action.text; break; default: break; }}dispatch({ type:UPDATE_TITLE_COLOR,color:'purple'});dispatch({ type:UPDATE_CONTENT_CONTENT,text:'新标题'});render(appState);复制代码
3.分装仓库
把状态放进一个容器里,将定义状态和规则的部分抽离到容器外面
function renderTitle(title) { let titleEle=document.querySelector('#title'); titleEle.innerHTML=title.text; titleEle.style.color=title.color;}function renderContent(content) { let contentEle=document.querySelector('#content'); contentEle.innerHTML=content.text; contentEle.style.color=content.color;}function render(appState) { renderTitle(appState.title); renderContent(appState.content);}// 容器function createStore(reducer) { let state; // 让外面可以获取状态 function getState() { return state; } function dispatch(action) { state=reducer(state,action); } dispatch({}); // 将方法暴露给外面使用,将状态放到了容器中外部无法在进行更改了 return { getState , dispatch }}// 容器一般会封装成库// 将定义状态和规则的部分抽离到容器外面,再传进去let initState={ title: {color: 'red',text: '标题'}, content:{color:'green',text:'内容'}}const UPDATE_TITLE_COLOR = 'UPDATE_TITLE_COLOR';const UPDATE_CONTENT_CONTENT = 'UPDATE_CONTENT_CONTENT';// 用户自己定义的规则,我们叫它reducer,也就是所谓的管理员// reducer要有两个参数,要根据老的状态和新传递的动作算出新的状态// 如果想获取默认状态,有一种方式,就是调用reducer,让每一个规则都不匹配将默认值返回// 在reducer中,reducer是一个纯函数,每次需要返回一个新的状态,只承担计算 State 的功能let reducer=function (state=initState,action) { switch (action.type) { case UPDATE_TITLE_COLOR: return {...state,title: {...state.title,color:action.color}}; case UPDATE_CONTENT_CONTENT: return {...state,content: {...state.content,text:action.text}}; break; default: return state; }}let store=createStore(reducer);render(store.getState());setTimeout(function () { store.dispatch({ type:UPDATE_TITLE_COLOR,color:'purple'}); store.dispatch({ type:UPDATE_CONTENT_CONTENT,text:'新标题'}); render(store.getState());},2000);复制代码
4.监控数据变化
function renderTitle(title) { let titleEle=document.querySelector('#title'); titleEle.innerHTML=title.text; titleEle.style.color=title.color;}function renderContent(content) { let contentEle=document.querySelector('#content'); contentEle.innerHTML=content.text; contentEle.style.color=content.color;}function render() { renderTitle(store.getState().title); renderContent(store.getState().content);}function createStore(reducer) { let state; let listeners=[]; function getState() { return state; } // 发布订阅模式,先将render方法订阅好,每次dispatch时都调用订阅好的方法 function dispatch(action) { // 发布 state=reducer(state,action); listeners.forEach(l=>l()); } function subscribe(listener) { // 订阅 listeners.push(listener); return () => { // 再次调用时 移除监听函数 listeners = listeners.filter(item => item!=listener); console.log(listeners); } } dispatch({}); return { getState,dispatch,subscribe }}let initState={ title: {color: 'red',text: '标题'}, content:{color:'green',text:'内容'}}let reducer=function (state=initState,action) { switch (action.type) { case UPDATE_TITLE_COLOR: return {...state,title: {...state.title,color:action.color}}; case UPDATE_CONTENT_CONTENT: return {...state,content: {...state.content,text:action.text}}; break; default: return state; }}let store=createStore(reducer);render();let unsubscribe = store.subscribe(render);setTimeout(function () { store.dispatch({ type:'UPDATE_TITLE_COLOR',color:'purple'}); unsubscribe(); store.dispatch({ type:'UPDATE_CONTENT_CONTENT',text:'新标题'});},2000);复制代码