1、抽离redux用法
1)原生
function renderApp(){
// react 把数据叫做类的状态,这里我们声明两个变量代表两条数据
let titleState = {
color:'red',
text:'标题'
}
let contentState = {
color:'green',
text:'内容'
}
let title = document.querySelector('.title');
title.innerHTML = titleState.text;
title.style.color = titleState.color;
let content = document.querySelector('.content');
content.innerHTML = contentState.text;
content.style.color = contentState.color;
}
renderApp();
复制代码
2)由于这样写代码耦合性高,我们把每个方法拆解开写成独立的方法
function renderTitle(titleState) {
let title = document.querySelector('.title');
title.innerHTML = titleState.text;
title.style.color = titleState.color;
}
function renderContent(contentState) {
let content = document.querySelector('.content');
content.innerHTML = contentState.text;
content.style.color = contentState.color;
}
function renderApp(){
let titleState = {
color:'red',
text:'标题'
}
let contentState = {
color:'green',
text:'内容'
}
renderTitle(titleState);
renderContent(contentState);
}
renderApp();
复制代码
这样每个方法都可以看成是一个组件:
组件:renderTitle(titleState)
组件:renderContent(contentState)
组件:renderApp()
相当于父组件renderApp()定义了数据titleState和contentState,儿子通过父组件传参使用数据。
3)如果函数嵌套函数时,嵌套传参就很麻烦,所以我们把数据全局变量化,就不用传参了
let titleState = { color:'red',text:'标题'};
let contentState = {color:'green',text:'内容'};
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = titleState.text;
title.style.color = titleState.color;
}
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = contentState.text;
content.style.color = contentState.color;
}
function renderApp(){
renderTitle();
renderContent();
}
renderApp();
复制代码
4)但是这样会声明大量的全局变量,所以需要把这些状态合并成一个变量state
redux的特点就是:“统一”的状态管理
let state = {
titleState:{color:'red',text:'标题'},
contentState:{color:'green',text:'内容'}
};
function renderTitle() {
let title = document.querySelector('.title');
// 4、通过state获取数据
title.innerHTML = state.titleState.text;
title.style.color = state.titleState.color;
}
function renderContent() {
let content = document.querySelector('.content');
// 4、通过state获取数据
content.innerHTML = state.contentState.text;
content.style.color = state.contentState.color;
}
function renderApp(){
renderTitle();
renderContent();
}
renderApp();
复制代码
5)但是这样写,用户可以直接更改状态,会出bug。所以我们写一个方法dispatch(action)去改状态,并且只能调这个方法才能改状态
dispatch()派发,需要接收一个参数,参数是action动作,规定action是一个对象,这个对象必须有一个type属性{type:'自定义'},通过type去标识要改什么
redux特点就是统一的状态管理,并且不能直接更改状态(用户不能操作这个状态)
let state = {
titleState:{color:'red',text:'标题'},
contentState:{color:'green',text:'内容'}
};
// 宏 常量,常量一般都大写
const CHANGE_TITLE_TEXT = 'change_title_text';
const CHANGE_CONTENT_COLOR = 'change_content_color';
function dispatch(action) {// 派发 需要接收一个参数,参数是action动作,规定action是一个对象,
// 这个对象必须有一个type属性{type:'自定义'},通过type去标识要改什么
switch (action.type){// 更改状态 要有一个新的状态(对象)覆盖掉 这里用展开运算符去保存原有的,替换掉改过得部分
case CHANGE_TITLE_TEXT:
state = {...state,titleState:{...state.titleState,text:action.text}}
break;
case CHANGE_CONTENT_COLOR:
state = {...state,contentState:{...state.contentState,color:action.color}}
break;
}
}
renderApp();
setTimeout(()=>{
// // 不能直接操作状态,通过函数dispatch()去操作更改
// dispatch({type:'change_title_text',text:'长标题'});// 除了type参数,其它参数叫payload载荷
// dispatch({type:'change_title_color',color:'blue'});// 除了type参数,其它参数叫payload载荷
// 这里type属性值将会多次使用并且不会改(就是一个标识),对于多次使用并且不会改的名字我们都提出去,(叫:宏 常量)
// 用一个常量保存,写常量有一个好处就是有提示,不会写错,便于函数dispatch去循环这个type并去执行相应逻辑
dispatch({type:CHANGE_TITLE_TEXT,text:'长标题'});
dispatch({type:CHANGE_CONTENT_COLOR,color:'blue'});
// 每次派发完都需要render从新渲染
renderApp();
},3000);
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = state.titleState.text;
title.style.color = state.titleState.color;
}
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = state.contentState.text;
content.style.color = state.contentState.color;
}
function renderApp(){
renderTitle();
renderContent();
}
复制代码
6)但是这样写,用户可以直接改这个全局变量state,这时,我们通过一个函数去保护这个作用域
// 将状态state放到一个盒子里 别人改不了
function createStore() {
let state = {
titleState:{color:'red',text:'标题'},
contentState:{color:'green',text:'内容'}
};
// dispatch函数也要放到这个状态里,才能拿到state数据
function dispatch(action) {// 派发 需要接收一个参数,参数是action动作,规定action是一个对象,
// 这个对象必须有一个type属性{type:'自定义'},通过type去标识要改什么
switch (action.type){// 更改状态 要有一个新的状态覆盖掉 这里用展开运算符去保存原有的,替换掉改过得部分
case CHANGE_TITLE_TEXT:
state = {...state,titleState:{...state.titleState,text:action.text}}
break;
case CHANGE_CONTENT_COLOR:
state = {...state,contentState:{...state.contentState,color:action.color}}
break;
}
}
// 但是外面无法使用这个dispatch函数和state状态,这里如果return {state}出去,用户还是能操作这个状态
// 所以我们写个方法getState(对外抛出一个状态),通过这个方法去改变状态,getState的目的是克
// 隆state这个对象并且暴露出去。这样就私有化了原有对象state,用户可以改暴露出去的对象而不影
// 响原有状态
let getState = () => JSON.parse(JSON.stringify(state));// 获取状态的方法
return {
getState,dispatch
}
}
// 通过store去调用createStore()返回的getState,dispatch方法
let store = createStore();
// 宏 常量,常量一般都大写
const CHANGE_TITLE_TEXT = 'change_title_text';
const CHANGE_CONTENT_COLOR = 'change_content_color';
renderApp();
setTimeout(()=>{
// // 不能直接操作状态,通过函数dispatch()去操作更改
// dispatch({type:'change_title_text',text:'长标题'});// 除了type参数,其它参数叫payload载荷
// dispatch({type:'change_title_color',color:'blue'});// 除了type参数,其它参数叫payload载荷
// 这里type属性值将会多次使用并且不会改(就是一个标识),对于多次使用并且不会改的名字我们都提出去,(叫:宏 常量)
// 用一个常量保存,写常量有一个好处就是有提示,不会写错,便于函数dispatch去循环这个type并去执行相应逻辑
store.dispatch({type:CHANGE_TITLE_TEXT,text:'长标题'});
store.dispatch({type:CHANGE_CONTENT_COLOR,color:'blue'});
// 每次派发完都需要render从新渲染
renderApp();
},3000);
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = store.getState().titleState.text;
title.style.color = store.getState().titleState.color;
}
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = store.getState().contentState.text;
content.style.color = store.getState().contentState.color;
}
function renderApp(){
renderTitle();
renderContent();
}
复制代码
7)但是这样写,createStore()看成一个库,每次用户使用这个库的时候需要改这个库里面的东西,这不靠谱,
所以这个state状态和dispatch函数里面的switch应该是用户自己定义好的,用户想怎么改就怎么改。
所以state状态和dispatch函数里面的switch逻辑不应该放到我们库里面去写,而是用户从外面传进来。(要有这的一个思想)。
所以我们首先把state拿出来,但是state还是要有,到时候会用用户默认传过来的对象覆盖掉reateStore里面state状态,
所以1、把state从createStore里面拿出去,用initState变量保存
2、createStore里的switch也应该拿出去,到时候一调某个方法的时候,目的使新状态覆盖掉老状态就可以了。这里我们用reducer()这个方法去处理更改状态的逻辑。但是这个reducer()方法里应该有state,action两个参 数,状态和动作。然后通过判断action.type,但是这里面的state不能通过赋值去更改了,因为在createStore() 的dispatch(action)里要去调用这个reducer()方法,并且去返回一个新的状态覆盖掉createStore()里面的state。
所以我们需要3、把reducer传给createStore(),在createStore()一调dispatch()就让reducer()执行,4、并且返 回一个新的状态覆盖掉老状态,5、所以在函数reducer()里应该是去返回新的state。
但是createStore(reducer)里面的默认状态state是undefiend,所以dispatch()里面的reducer(state,action)参 数state是undefiend,当走到5、去return一个新state无法执行。所以,我们一开始就应该把initState赋值给 createStore(reducer)里面的state,所以首先我们在2、把initState赋值给state,并且返回这个state,然后 6、内部调用dispatch({})并且派发一个空对象,这个时候执行dispatch(action)的参数action是一个空对象, state是undefiend,执行到2、state就被默认initState赋值,由于action是空对象,所以会直接执行到最后一步, return state,返回状态,这时候createStore(reducer)里面的state就被返回的这个状态(initState)赋值了
const CHANGE_TITLE_TEXT = 'change_title_text';
function createStore(reducer) {
let state;
function dispatch(action) {
// 4、通过调用写好的方法reducer()返回一个新的状态覆盖掉老状态,
state = reducer(state,action);
}
// 6、调用dispatch({})
dispatch({});
let getState = () => JSON.parse(JSON.stringify(state));
return {getState,dispatch}
}
// 以上是库里面的源代码
// 以下是用户的逻辑
// 1、拿出state 初始状态
let initState = {
titleState:{color:'red',text:'标题'},
contentState:{color:'green',text:'内容'}
}
// 3、把reducer传给createStore
let store = createStore(reducer);//创建容器时需要传递一个管理员,用来改状态
// 2、拿出switch
function reducer(state=initState,action) { //管理员,负责如何更改状态。reducer应该有action和state参数
switch (action.type){
case CHANGE_TITLE_TEXT:
// state = {...state,titleState:{...state.titleState,text:action.text}}
// 5、这里是去return一个新状态
return {...state,titleState:{...state.titleState,text:action.text}}
}
return state;
}
renderApp();
setTimeout(()=>{
store.dispatch({type:CHANGE_TITLE_TEXT,text:'长标题'});
renderApp();
},3000);
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = store.getState().titleState.text;
title.style.color = store.getState().titleState.color;
}
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = store.getState().contentState.text;
content.style.color = store.getState().contentState.color;
}
function renderApp(){
renderTitle();
renderContent();
}
复制代码
8)最后总结:基本的redux工作流程
1、createStore()方法
2、createStore()方法里面有dispatch()和getState,
-
getState主要四获取我们createStore()里面的state状态的
-
dispatch()根据当前reducer()去更改createStore()里面的state状态,
2、redux
如果以上1、抽离redux用法看明白了,接下来我们来自己实现redux
// 1、创建容器createStore
function createStore(r) { // 通过r改变state
// 5、此时默认还是undefined
let state;
// 6、内部派发,调用dispatch,执行r,让r的返回值覆盖容器里的state
// dispatch是唯一改变状态的方法
function dispatch(action) {
state = r(state,action);
}
// 7、执行dispatch,目的是用用户的状态覆盖掉自身的状态
dispatch({});
// 8、克隆状态
let getState = () => JSON.parse(JSON.stringify(state));
// 9、返回方法
return {getState,dispatch}
}
// 以上就是redux的基本流程
// 以下是需要用户自己写的
// 10、常量 要改的内容
const CHANGE_TITLE = 'change_title';
// 3、reducer的状态设置默认值,并返回这个默认值去覆盖掉容器createStore里的状态state
function reducer(state={title:'标题'},action) {
console.log(action);
// 11、判断更改状态
switch (action.type){
case CHANGE_TITLE:// {type:CHANGE_TITLE,content:'xxx'}
return {...state,title:action.content}
}
// 4、返回状态默认值,覆盖掉容器里的状态state
return state;
}
// 2、传递一个管理员reducer
let store = createStore(reducer);
// 11、获取标题元素渲染title store.getState()拿到的是state对象
function render() {
document.querySelector('.title').innerHTML = store.getState().title
}
render();
// 12、第二次通过dispatch去更改标题,dispatch是唯一改变状态的方法
setTimeout(function () {
store.dispatch({type:CHANGE_TITLE,content:'噢耶'});
render();
},2000);
// 第一次渲染内容是直接通过赋值state去渲染的,并没有走11、swich(可以注释了看)
复制代码
整个流程:
createStore里面放了个状态state,状态不能直接改
只有通过reducer去更改这个状态,这个reducer是用户传递过来的
用户写的函数只有通过dispatch({type:'类型',data:'xxx'})去派发一个对象去传给reducer改变状态
状态改变后,在通过render渲染这个视图
但是由于每次调用dispatch都要render渲染,所以我们通过发布订阅,先把事件订阅好,只要dispatch就执行render方法,或者执行其他方法 (先订阅事件存放到列表中,当这些事件发生了就依次执行)
发布订阅的代码从13开始
// 1、创建容器createStore
function createStore(r) { // 通过r改变state
// 5、此时默认还是undefined
let state;
// 6、内部派发,调用dispatch,执行r,让r的返回值覆盖容器里的state
// dispatch是唯一改变状态的方法
function dispatch(action) {
state = r(state,action);
// 16、一调用dispatch就执行listeners中存放的函数
listeners.forEach(item=>item());
}
// 13、存放所有监听函数
let listeners = [];
// 14、一订阅就把事件存放到listeners中
let subscribe = (fn) =>{
listeners.push(fn);
return ()=>{ // 取消绑定的函数,调用可以删除的函数
listeners = listeners.filter(item=>item!==fn);
}
};
// 7、执行dispatch,目的是用用户的状态覆盖掉自身的状态
dispatch({});
// 8、克隆状态
let getState = () => JSON.parse(JSON.stringify(state));
// 9、返回方法
return {getState,dispatch,subscribe}
}
// 以上就是redux的基本流程
// 以下是需要用户自己写的
// 10、常量 要改的内容
const CHANGE_TITLE = 'change_title';
// 3、reducer的状态设置默认值,并返回这个默认值去覆盖掉容器createStore里的状态state
function reducer(state={title:'标题'},action) {
console.log(action);
// 11、判断更改状态
switch (action.type){
case CHANGE_TITLE:// {type:CHANGE_TITLE,content:'xxx'}
return {...state,title:action.content}
}
// 4、返回状态默认值,覆盖掉容器里的状态state
return state;
}
// 2、传递一个管理员reducer
let store = createStore(reducer);
// 11、获取标题元素渲染title store.getState()拿到的是state对象
function render() {
document.querySelector('.title').innerHTML = store.getState().title
}
render();
// 15、
store.subscribe(render);
store.subscribe(function () {
alert(1)
});
// 12、第二次通过dispatch去更改标题,dispatch是唯一改变状态的方法
setTimeout(function () {
store.dispatch({type:CHANGE_TITLE,content:'噢耶'});
// render();
},2000);
// 第三次通过dispatch去更改标题,dispatch是唯一改变状态的方法
setTimeout(function () {
store.dispatch({type:CHANGE_TITLE,content:'噢耶1'});
// render();
},3000);
复制代码
希望过2秒后,执行unSubscribe(),可以把数组listeners里面的函数删掉,(取消订阅事件)
从17、subscribe执行完再返回一个函数去过滤listeners里面的函数
// 1、创建容器createStore
function createStore(r) { // 通过r改变state
// 5、此时默认还是undefined
let state;
// 6、内部派发,调用dispatch,执行r,让r的返回值覆盖容器里的state
// dispatch是唯一改变状态的方法
function dispatch(action) {
state = r(state,action);
// 15、一调用dispatch就执行listeners中存放的函数
listeners.forEach(item=>item());
}
// 13、存放所有监听函数
let listeners = [];
// 14、一订阅就把事件存放到listeners中
let subscribe = (fn) =>{
listeners.push(fn);
// 17、subscribe执行完再返回一个函数去取消绑定的函数,调用可以删除的函数
return ()=>{
listeners = listeners.filter(item=>item!==fn);
}
};
// 7、执行dispatch,目的是用用户的状态覆盖掉自身的状态
dispatch({});
// 8、克隆状态
let getState = () => JSON.parse(JSON.stringify(state));
// 9、返回方法
return {getState,dispatch,subscribe}
}
// ---------------------------以上就是redux的基本流程----------------------
// ---------------------------以下是需要用户自己写的-----------------------
// 10、常量 要改的内容
const CHANGE_TITLE = 'change_title';
// 3、reducer的状态设置默认值,并返回这个默认值去覆盖掉容器createStore里的状态state
function reducer(state={title:'标题'},action) {
console.log(action);
// 11、判断更改状态
switch (action.type){
case CHANGE_TITLE:// {type:CHANGE_TITLE,content:'xxx'}
return {...state,title:action.content}
}
// 4、返回状态默认值,覆盖掉容器里的状态state
return state;
}
// 2、传递一个管理员reducer
let store = createStore(reducer);
// 11、获取标题元素渲染title store.getState()拿到的是state对象
function render() {
document.querySelector('.title').innerHTML = store.getState().title
}
render();
// 15、
store.subscribe(render);
// 18、第一次调store.subscribe()执行alert(1)
let unSubscribe = store.subscribe(function () {
alert(1)
});
// 12、第二次通过dispatch去更改标题,dispatch是唯一改变状态的方法
setTimeout(function () {
store.dispatch({type:CHANGE_TITLE,content:'噢耶'});
// 19、第二次执行unSubscribe(),就通过17、过滤解绑所以不会再执行alert(1)
unSubscribe();
// render();
},2000);
setTimeout(function () {
store.dispatch({type:CHANGE_TITLE,content:'噢耶1'});
// render();
},3000);
复制代码
总结:redux常用的三个方法:dispatch()状态改变、getState()状态克隆、subscribe()发布订阅,整个rudex源码如下:
function createStore(r) {
let state;// 状态
function dispatch(action) { // 派发
state = r(state,action);// 改变状态
listeners.forEach(item=>item());
}
let listeners = [];
let subscribe = (fn) =>{//发布订阅
listeners.push(fn);
return ()=>{//解绑
listeners = listeners.filter(item=>item!==fn);
}
};
dispatch({});//传递空对象,第一次执行时返回用户传递的默认状态覆盖state
let getState = () => JSON.parse(JSON.stringify(state));//克隆状态
return {getState,dispatch,subscribe};//返回函数
}
复制代码
用redux来写个计数器列子
原生写法
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="add">+</button>
<div id="text"></div>
<button id="minus">-</button>
<!--引入自己写好的redux.js库-->
<script src="redux.js"></script>
<script src="counter.js"></script>
</body>
</html>
复制代码
counter.js
// 使用redux流程
// 1、定义当前项目有什么功能(常量)
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 2、定义当前项目的默认状态,状态放到reducer中,
// reducer默认要返回一个状态为初始状态 => state={number:0}
// r({number:0},{type:INCREMENT,amount:3})
function reducer(state={number:0},action) {
// 6、匹配动作进行更改
switch (action.type){
case INCREMENT:
return{number:state.number+action.amount}//{number:3}
}
return state;
}
// 3、创建容器createStore 通过getSate拿到state
let store = createStore(reducer);// store.getState() = {number:0}
// 4、可以在外面进行派发动作
// 默认渲染一次
function render() {
text.innerHTML = store.getState().number
}
render();//初始化
//5、订阅状态
store.subscribe(render);//当状态变化时触发render函数
add.addEventListener('click',function () {
store.dispatch({type:INCREMENT,amount:3});// 通过dispatch修改状态
},false);
复制代码
redux.js
function createStore(r) {
let state; // 此时默认还是undefined
function dispatch(action) { // 派发 r({number:0},{type:INCREMENT,amount:3})
state = r(state,action);
listeners.forEach(item=>item());
}
let listeners = [];// 存放所有的监听函数 [render]
let subscribe = (fn) =>{
listeners.push(fn);
return ()=>{ // 取消绑定的函数,调用可以删除函数
listeners = listeners.filter(item=>item!==fn);
}
};
dispatch({}); // 目的是用用户的状态覆盖掉自身的状态
let getState = () => JSON.parse(JSON.stringify(state));
return {getState,dispatch,subscribe};
}
复制代码
整个流程: conunter.js是用户写的,redux.js是库。这里没标注是redux.js的都时在conunter.js执行的
conunter.js =>定义当前项目功能(常量)
=> reducer作为参数放到createStore里,createStore是redux提供的(现在我们用的是自己写的)=> let store = createStore(reducer),这时候去执行
=> redux.js的createStore()
=>此时createStore()里state是undefined,
=> 执行到dispatch({}),action是空对象,
=> 执行到r(state,action),这个r就是reducer,此时r里的state是undefined,action是空对象
=> 此时会去执行counter.js里的reducer(),此时state有了默认值number:0,action是空对象,所以reducer()执行完的结果是返回number:0
=> 回到redux.js,createStore()里state被赋值成number:0,并且createStore()里有getState()方法,所以最后conunter.js里的let store = createStore(reducer)这一步执行完就是=> store.getState() = {number:0}
=> 所以执行到render()默认渲染的时候,拿到的就是tore.getState().number=>0,把0插入到text.innerHTML
=>然后我们把render传给了store.subscribe(render),
=> 在redux.js执行subscribe,这是数组listeners多了个render函数,
=> 每次点击事件时会触发dispatch,dispatch里面传了个对象,这个对象会由redux.js的dispatch的action接收并且传递给r(state,action)中的action,
=> 此时state已被赋值,
所以r(state,action) => r({number:0},{type:INCREMENT,amount:3})
=> 这时候会触发counter.js中的reducer执行,这次执行
=> state不再是默认值,是传过来的值{number:0}(只有初始化的时候会是默认值),action是传过来的对象{type:INCREMENT,amount:3},
=> 这时候会去执行reduce中的switch,
=> return{number:state.number+action.amount} => {number:3},这个{number:3}会返回给redux.js的dispatch()方法中去,当dispatch()中的r()执行完后,会把这个{number:3}赋值给state。
=> 这时候执行render(),此时store.getState().number拿到的是{number:3},所以text.innerHTML被从新渲染成3
3、在react中应用redux
如果原生写法理解了,在react中去应用redux吧
redux.js,这里的redux.js需要加一个导出的步骤
function createStore(r) {
let state;
function dispatch(action) {
state = r(state,action);
listeners.forEach(item=>item());
}
let listeners = [];
let subscribe = (fn) =>{
listeners.push(fn);
return ()=>{
listeners = listeners.filter(item=>item!==fn);
}
};
dispatch({});
let getState = () => JSON.parse(JSON.stringify(state));
return {getState,dispatch,subscribe};
}
// 这里注意模块化的概念,要去导出这个createStore
export {createStore}
复制代码
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from './redux'
const INCREMENT = 'increment';// 增加
const DECREMENT = 'decrement';// 减少
function reducer(state={number:0},action) {
switch (action.type){
case INCREMENT:
return {number:state.number+action.amount}
}
return state;// 切记返回默认状态
}
let store = createStore(reducer);
class Counter extends React.Component{
// 目的是将redux中的状态映射到组件上,更改组件的状态就可以导致视图的刷新
constructor(){
super();
this.state = {number:store.getState().number}
}
// 每次dispatch后我们需要去更改状态,这里通过subscribe订阅去更改状态
componentDidMount(){ // 组件渲染完成
this.unsubscribe = store.subscribe(() => {
this.setState({
number:store.getState().number
})
});
}
// 如果组件销毁需要去移除事件监听(跟定时器一样)
componentWillUnmount(){
this.unsubscribe();
}
render(){
return(
<div>
<button onClick={()=>{
store.dispatch({type:INCREMENT,amount:3});
}}>+</button>
{/*<p>{store.getState().number}</p>*/}
<p>{this.state.number}</p>
<button>-</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.getElementById('root'));
// 需要注意的是:只有react的状态或者属性变了,才会导致页面刷新。
// 所以我们需要把getState().number这个数据放到react状态里面去,
// 当我们改数据的话,实时更新状态,更新状态就可以从新渲染页面。
复制代码
目录
4、应用redux
redux里包含的东西
1、createStore创建容器用来存放状态的
2、createStore中有一个state属性,不能直接访问状态
3、在组件中想要获取这个状态可以通过产生的容器中的getState方法来获取
4、createStore中的subscribe目的是用来订阅事件的(当用户派发新动作时会将订阅的函数进行执行)
5、派发函数dispatch,提供派发动作的方法,在内部会调用reducer,通过reducer触发状态的更新
6、action动作,动作有一个type属性用来描述会干什么事情
注意:redux都能用,vuex只能在vue中用
根据上面的步骤来写一个redux.jsfunction createStore(reducer) {// reducer是外界传入的,可以
// 让reducer执行,执行后默认会返回初始化的状态
let state;//默认是undefined
let getState = () =>{//组件获取状态的
return JSON.parse(JSON.stringify(state));
};
let listeners = [];
let subscribe = (fn) =>{//订阅状态发生变化时需要触发的函数
listeners.push(fn);
return function () {
listeners = listeners.filter(item=>item!==fn);//;调用二次的时候可以把函数移除掉
}
};
let dispatch = (action) =>{//用来组件派发动作的
state = reducer(state,action);//reducer是根据老状态和派发的动作返回一个新的状态,
// 覆盖掉老状态
listeners.forEach(item=>item());//状态变化时 重新执行订阅的事件
};
dispatch({});//初始化redux的默认状态
return {getState,subscribe,dispatch}
}
// 导出createStore
// module.exports = createStore;
export {createStore}
复制代码
完成项目
还是计数器的列子,两个组件:一个是计数器,一个是显示奇偶数,这两个组件平级关系。
在以前我们只有找一个共同父级去传递状态(见父子组件的传递)。
现在我们把状态统一放一个地方,所有组件都用这个状态,不用管组件之间的关系是平级还是父子还是后代嵌套。
这个状态是用户传递给createStore
所有组件不能直接去更改状态,只能通过dispatch告知给管理器reducer去更改状态,reducer也是用户定义好传递给createStore。dispatch必须有个type属性,管理器reducer通过这个属性值判断并且更改状态,
状态更新后,所有组件通过subscribe订阅执行setState事件去从新渲染页面
目录:
Compute.jsimport React,{Component} from 'react';
import store from '../store';
export default class Compute extends React.Component {
constructor(){
super();
this.state = {n:store.getState().n
umber}
}
componentDidMount(){
this.un = store.subscribe(()=>{
this.setState({n:store.getState().number})
})
}
componentWillUnmount(){
this.un();// 组件销毁时 需要将其监听的函数移除掉
}
render(){
return (
<div>{this.state.n%2?'奇数':'偶数'}</div>
)
}
}
复制代码
Counter.js
import React,{Component} from 'react';
import store from '../store';
//组件更新 属性的更新 更新状态,将redux的数据转化成自己的状态
export default class Counter extends React.Component {
constructor(){
super();
this.state = {n:store.getState().number}
}
componentDidMount(){
this.un = store.subscribe(()=>{
this.setState({n:store.getState().number})
})
}
componentWillUnmount(){
this.un();// 组件销毁时 需要将其监听的函数移除掉
}
render(){
return (
<div>
<button onClick={()=>{
store.dispatch({type:'ADD',count:1})
}}>+</button>
<span>{this.state.n}</span>
<button onClick={()=>{
store.dispatch({type:'MINUS',count:1})
}}>-</button>
</div>
)
}
}
复制代码
store/index.js
import {createStore} from '../redux';
function reducer(state={number:0},action) {
switch (action.type){
case 'ADD':
return {number:state.number+action.count};
case 'MINUS':
return {number:state.number-action.count};
}
return state;
}
export default createStore(reducer)
复制代码
index.js
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Counter from "./components/Counter";
import Compute from "./components/Compute";
ReactDOM.render(<div>
<Counter/>
<Compute/>
</div>,window.root);
复制代码
redux.js
function createStore(reducer) {// reducer是外界传入的,可以
// 让reducer执行,执行后默认会返回初始化的状态
let state;//默认是undefined
let getState = () =>{//组件获取状态的
return JSON.parse(JSON.stringify(state));
};
let listeners = [];
let subscribe = (fn) =>{//订阅状态发生变化时需要触发的函数
listeners.push(fn);
return function () {
listeners = listeners.filter(item=>item!==fn);//;调用二次的时候可以把函数移除掉
}
};
let dispatch = (action) =>{//用来组件派发动作的
state = reducer(state,action);//reducer是根据老状态和派发的动作返回一个新的状态,
// 覆盖掉老状态
listeners.forEach(item=>item());//状态变化时 重新执行订阅的事件
};
dispatch({});//初始化redux的默认状态
return {getState,subscribe,dispatch}
}
// 导出createStore
// module.exports = createStore;
export {createStore}
复制代码
test.js
let createStore = require('./redux');
function reducer(state={number:0},action) {
switch (action.type){
case 'add':
return {number:state.number+action.b}
}
return state;
}
let store = createStore(reducer);
console.log(store.getState());
let un = store.subscribe(function () {
console.log(store.getState());
});
store.dispatch({type:'add',b:1});
// un();
store.dispatch({type:'add',b:1});
store.dispatch({type:'add',b:1});
复制代码
5、合并reducer
安装redux,以上列子引入的都是自己写的redux
在上面的列子上增加个todo组件,这里会涉及到合并reducer
目录:
Compute.js
import React,{Component} from 'react';
import store from '../store';
export default class Compute extends React.Component {
constructor(){
super();
this.state = {n:store.getState().counter.number}
}
componentDidMount(){
this.un = store.subscribe(()=>{
this.setState({n:store.getState().counter.number})
})
}
componentWillUnmount(){
this.un();// 组件销毁时 需要将其监听的函数移除掉
}
render(){
return (
<div>{this.state.n%2?'奇数':'偶数'}</div>
)
}
}
复制代码
Counter.js
import React,{Component} from 'react';
import store from '../store';
//组件更新 属性的更新 更新状态,将redux的数据转化成自己的状态
export default class Counter extends React.Component {
constructor(){
super();
this.state = {n:store.getState().counter.number}
}
componentDidMount(){
this.un = store.subscribe(()=>{
this.setState({n:store.getState().counter.number})
})
}
componentWillUnmount(){
this.un();// 组件销毁时 需要将其监听的函数移除掉
}
render(){
return (
<div>
<button onClick={()=>{
store.dispatch({type:'ADD',count:1})
}}>+</button>
<span>{this.state.n}</span>
<button onClick={()=>{
store.dispatch({type:'MINUS',count:1})
}}>-</button>
</div>
)
}
}
复制代码
Todo.js
import React,{Component} from 'react';
import store from '../store';
export default class Todo extends React.Component {
render(){
return (
<div>
<input type="text" onKeyDown={(e)=>{
store.dispatch({type:'ADDTODO',content:e.target.value})
}}/>
</div>
)
}
}
复制代码
store/index.js
import {createStore,combineReducers} from '../redux';
function counter(state={number:0},action) {
switch (action.type){
case 'ADD':
return {number:state.number+action.count};
case 'MINUS':
return {number:state.number-action.count};
}
return state;
}
function todo(state=[],action) {
switch (action.type){
case 'ADDTODO':
return [...state,action.content]
}
return state;
}
// //-----------------这个方法不用我们写 放到redux里去-----------------
// // 返回一个新的reducer 还是一个函数
// let combineReducers = (reducers) => { // reducers{counter:fn,todo:fn}
// console.log(reducers);
// let obj = {}; //最终的状态
// return (state,action)=>{// 第二次执行这个函数时,state={counter:{number:0},todo:[]}
// for(let key in reducers){
// obj[key] = reducers[key](state[key],action);// obj.counter = {number:0}
// }
// return obj
// }
// };
// // combineReducers 合并reducer(把counter和todo合并以后传递给createStore)
// // 状态也要合并,合并后:{counter:{number:0},todo:[]}
// //-----------------这个方法不用我们写 放到redux里去-----------------
// 这里需要把两个reducer=>counter和todo合并成一个reducer
let reducer = combineReducers({
counter,todo
});
export default createStore(reducer);
复制代码
index.js
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Counter from "./components/Counter";
import Compute from "./components/Compute";
import Todo from "./components/Todo";
ReactDOM.render(<div>
<Counter/>
<Compute/>
<Todo/>
</div>,window.root);
复制代码
redux.js
function createStore(reducer) {// reducer是外界传入的,可以
// 让reducer执行,执行后默认会返回初始化的状态
let state;//默认是undefined
let getState = () =>{//组件获取状态的
return JSON.parse(JSON.stringify(state));
};
let listeners = [];
let subscribe = (fn) =>{//订阅状态发生变化时需要触发的函数
listeners.push(fn);
return function () {
listeners = listeners.filter(item=>item!==fn);//;调用二次的时候可以把函数移除掉
}
};
let dispatch = (action) =>{//用来组件派发动作的
state = reducer(state,action);//reducer是根据老状态和派发的动作返回一个新的状态,
// 覆盖掉老状态
listeners.forEach(item=>item());//状态变化时 重新执行订阅的事件
};
dispatch({});//初始化redux的默认状态
return {getState,subscribe,dispatch}
}
//---------------------------------------合并reducer-----------------------
// 返回一个新的reducer 还是一个函数
let combineReducers = (reducers) => { // reducers{counter:fn,todo:fn}
return (state={},action)=>{// 第二次执行这个函数时,state={counter:{number:0},todo:[]}
let obj = {}; //最终的状态
for(let key in reducers){
obj[key] = reducers[key](state[key],action);// obj.counter = {number:0}
}
return obj
}
};
// combineReducers 合并reducer(把counter和todo合并以后传递给createStore)
// 状态也要合并,合并后:{counter:{number:0},todo:[]}
//---------------------------------------合并reducer-----------------------
// 导出createStore
// module.exports = createStore;
export {createStore,combineReducers}
复制代码
test.js
let createStore = require('./redux');
function reducer(state={number:0},action) {
switch (action.type){
case 'add':
return {number:state.number+action.b}
}
return state;
}
let store = createStore(reducer);
console.log(store.getState());
let un = store.subscribe(function () {
console.log(store.getState());
});
store.dispatch({type:'add',b:1});
// un();
store.dispatch({type:'add',b:1});
store.dispatch({type:'add',b:1});
复制代码