前端状态管理简易实现(以vuex为例)

1.状态管理到底是什么?有什么用?

       状态管理说白了就是一个 全局变量,var obj = {} 这样的东西。

       使用全局变量的缺点:

             无法追踪数据的更改情况,任意的操作都可导致数据变量,无法所追踪数据的变化过程,大型应用中不太容易定位bug

       状态管理:

              可以追踪到数据变化的全局变量。

        使用场景:

       在多页面中,状态管理应用不大,因为这个变量仅仅存与内存中,当跳转页面时,数据就会清除。当然,如果对状态进行存储(比如sessionStroge),倒也可以实现状态的复用,但是想想,每次数据都需存储,相比较sessionStroge api级别的存储,状态管理会更复杂一些,带来应用的复杂性。

        在单页面中,跳转系统转化为路由系统,路由跳转无需熟悉页面,保存到内存中的状态数据生命周期更长,使用也更加广泛。但依然要注意在web端,刷新操作的频率,刷新页面会使的数据全部丢失,所以需在代码中完成丢失情况的处理,使的代码更加健壮性。

2.那如何追踪一个全局变量的数据变化?

     如果我们有使用vuex的经验,我们知道在vuex里的数据,无法通过store.state.a = 1这样的赋值操作完成。因为这样,无法追踪到数据的变化。

     问题:追踪一个全局变量的数据变化?

     方法1:Object.defineProperty();这个应该很熟悉,vue双向绑定就是通过这里的setter getter实现的。

    方法2:Proxy ES6新语法,vue3的底层双向绑定会使用它。(本文我们以Proxy做demo)

学习Proxy,请点击 www.jianshu.com/p/34f0e6abe…

3.在写一个简易的状态管理,需要我们准备什么?

         前提:本文只讨论如何简易状态管理,所以性能方面不会考虑,也就是说不会使用Vnode diff算法等知识。


         查看上面的图,有几个问题我们需要考虑:

          1.只要state发生变化,view层自动更新?

          我们增加个订阅-发布器, 每个需要state的view订阅数据变化,当数据发生变化时,发布这个变化,state也会跟着变化。

         2.这个指令是什么?是vuex中的actions mutations吗?

         我们模拟vuex实现,所以认为指定就是这两个,actions是异步操作,mutation是同步操作。

4.代码实现:

        本文代码大部分来自juejin.im/post/5b7635…

        本文是在上文中总结学习写出的。

        本文目录结构如下:

              

1.订阅-发布模式 js/lib/pubsub.js
export default class PubSub{
    constructor(){
        this.events = {};
    }

    // 订阅
    subscribe(type,callback){
        if(!this.events.hasOwnProperty(type)){
            this.events[type] = [];
        }
        // 返回该type下订阅了多少个回掉函数
        // push返回的是当前数组的length
        return this.events[type].push(callback);
    }

    // 发布
    publish(type,data={}){
        if(!this.events.hasOwnProperty(type)){
            return [];
        }
        return this.events[type].map((callback)=>{
            callback(data)
        })
    }
}
复制代码

2.实现状态管理的关键  js/store/store.js
import pubSub from '../lib/pubsub.js'

export default class Store {
    // 默认参数
    /*
    * 以vuex的写法举例
    * new Vuex.store({
    *   state:{
    *
    *   },
    *   mutations:{},
    *   actions:{}
    * })
    * */
    constructor(params) {
        let self = this;
        self.mutations = params.mutations ? params.mutations : {};
        self.actions = params.actions ?  params.actions : {} ;
        this.events = new pubSub();
        self.state = new Proxy((params.state || {}), {
            set(target,key,value){
                target[key] = value;
                console.log(`stateChange: ${key}: ${value}`);
                // 当数据变化时,进行发布操作
                self.events.publish('stateChange',self.state);
                // 如果数据不是有mutation方式改变,则发出警告
                if(self.status !== 'mutation'){
                    console.warn(`数据应该用mutation方式提交`);
                }
                self.status = 'resting';
                return true;
            }
        });
        self.status = 'resting';
    }

    /*
    * dispatch():提交action,action是一个异步方法
    * */
    dispatch(actionType,payload){
        let self = this;
        if(typeof self.actions[actionType] !== 'function'){
            console.log(`action ${actionType} 不存在`);
            return false;
        }
        console.groupCollapsed(`ACTION: ${actionType}`);
        self.status = 'action';
        self.actions[actionType](self,payload);
        console.groupEnd();
        return true;
    }

    commit(mutataionType,payload){
        let self = this;
        if(typeof self.mutations[mutataionType] !== 'function'){
            console.log(`Mutation "${mutataionType}" 不存在`);
            return false;
        }
        console.log(`Mutation: ${mutataionType}`)
        self.status = 'mutation';
        let newState = self.mutations[mutataionType](self.state, payload);
        self.state = Object.assign(self.state,newState);
        return true;
    }


}




复制代码

实例化Store  js/store/index.js
import Store from './store.js'

export default new Store({
    state:{
        items:[
            1,2
        ]
    },
    mutations:{
        addItem(state,payload){
            state.items.push(payload);
            return state;
        },
        clearItem(state,payload){
            state.items.splice(payload.index,1);
            return state;
        }
    },
    actions:{
        addItem(context,payload){
            context.commit('addItem',payload)
        },
        clearItem(context,payload){
            context.commit('clearItem',payload)
        }
    }
})
复制代码

view层:js/lib/component.js
import Store from '../store/store.js'
export default class Component{
    constructor(props={}) {
        let self = this;
        this.render = this.render || function () {
            
        }
        // 关键:这个是通用的组件类,可对需要使用state的组件,进行数据订阅。
        if(props.store instanceof Store){
            props.store.events.subscribe('stateChange',self.render.bind(self))
        }
        if(props.hasOwnProperty('el')){
            self.el = props.el;
        }
    }

}复制代码

组件类:List      js/components/List.js
import component from '../lib/component.js'
import store from '../store/index.js'
export default class List extends component{
    constructor() {
        super({
            store,
            el: document.querySelector('.js-items')
        })
    }

        render(){
            let self = this;
            if(store.state.items.length === 0) {
                self.el.innerHTML = `<p class="no-items">You've done nothing yet &#x1f622;
</p>`;
                return;
            }

            self.el.innerHTML = `
                <ul class="app__items">
                ${store.state.items.map(item => {
                        return `
                    <li>${item}<button aria-label="Delete this item">×</button></li>
                    `
                    }).join('')}
                </ul>
            `;

            self.el.querySelectorAll('button').forEach((button, index) => {
                button.addEventListener('click', () => {
                    store.dispatch('clearItem', { index });
                });
            });

        }


}复制代码

Count组件   js/components/Count.js
import store from '../store/index.js'
import component from '../lib/component.js'
export default  class Count extends component{
    constructor() {
        super({
            store,
            el:document.querySelector('.js-count')
        })
    }

    render(){
        let suffix = store.state.items.length !== 1 ? 's' : '';
        let emoji = store.state.items.length > 0 ? '&#x1f64c;' : '&#x1f622;';
        this.el.innerHTML = `
            <small>You've done</small>
            ${store.state.items.length}
            <small>thing${suffix} today ${emoji}</small>
        `;
    }

}
复制代码

主函数:main.js
import store from './store/index.js';
import Count from './components/Count.js';
import List from './components/List.js';

const formElement = document.querySelector('.js-form');
const inputElement = document.querySelector('#new-item-field');
formElement.addEventListener('submit', evt => {
    evt.preventDefault();

    let value = inputElement.value.trim();

    if(value.length) {
        store.dispatch('addItem', value);
        inputElement.value = '';
        inputElement.focus();
    }
});
const countInstance = new Count();
const listInstance = new List();

countInstance.render();
listInstance.render();复制代码

入口html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="css/global.css" />
    <!--<link rel="stylesheet" media="all" href="https://rawgit.com/hankchizljaw/boilerform/master/dist/css/boilerform.min.css?v=1.1.1" />-->
    <title>Vanilla State Management</title>
</head>
<body>
    <main>
        <header class="intro">
            <h1 class="intro__heading">Done list</h1>
            <p class="intro__summary">A list of things that you have achieved today</p>
            <p class="intro__summary"><b>Note:</b> The data isn't stored, so it will disappear if you reload!</p>
        </header>
        <section class="app">
            <section class="app__input">
                <h2 class="app__heading">What you've done</h2>
                <div class="js-items" aria-live="polite" aria-label="A list of items you have done"></div>
                <form class=" new-item boilerform js-form">
                    <div class="boilerform">
                        <!-- Form styles from the https://boilerform.design boilerplate -->
                        <label for="new-item-field" class="new-item__label c-label ">Add a new item</label>
                        <input type="text" class=" new-item__details  c-input-field " id="new-item-field" autocomplete="off" />
                        <button class=" c-button  new-item__button ">Save</button>
                    </div>
                </form>
            </section>
            <aside class="app__status">
                <p role="status" class="visually-hidden">You have done <span class="js-status">1 thing</span> today!</p>
                <div class="app__decor js-count">
                    <small>You've done</small>
                    <span>1</span>
                    <small>things today ?</small>
                </div>
            </aside>
        </section>
    </main>
    <script type="module" src="js/main.js"></script>
</body>
</html>复制代码

5.总结

以上,也就实现了一下简易的状态管理,其实状态管理就是将{a:1}的数据修改都通过指定的指令去修改,可以追踪到数据变化,所以不要把状态管理想的多么复杂,多么高大上。时刻牢记,所谓状态管理就是一个全局对象,只是这个对象的属性变化要经过规定一套流程。

如果你不喜欢状态管理,而且项目不大的情况下,可以不用。sessionstorage这些也是很好的选择。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值