从js源码到Redux设计模式深入浅出Redux应用

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.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}
复制代码

完成项目

还是计数器的列子,两个组件:一个是计数器,一个是显示奇偶数,这两个组件平级关系。

在以前我们只有找一个共同父级去传递状态(见父子组件的传递)。

现在我们把状态统一放一个地方,所有组件都用这个状态,不用管组件之间的关系是平级还是父子还是后代嵌套。

这个状态是用户传递给createStore

所有组件不能直接去更改状态,只能通过dispatch告知给管理器reducer去更改状态,reducer也是用户定义好传递给createStore。dispatch必须有个type属性,管理器reducer通过这个属性值判断并且更改状态,

状态更新后,所有组件通过subscribe订阅执行setState事件去从新渲染页面

目录:

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().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});
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值