当前位置: 首页 > Web前端 > JavaScript

自己写一个redux_2

时间:2023-03-26 21:23:59 JavaScript

提起Redux,我们最先想到的就是React-redux这个库,但其实Redux和React-redux并不是一回事。Redux是从Flux衍生出来的一种架构模式。React-redux是Redux思想与React相结合的具体实现。我们在使用React的时候,经常会遇到组件嵌套很深,需要传递值的情况。使用道具进行价值传递显然是非常痛苦的。为了解决这个问题,React为我们提供了一个原生的contextAPI,但是我们使用最多的解决方案是使用React-redux这个基于contextAPI封装的库。本文不介绍React-redux的具体用法,而是通过一个小例子来了解什么是redux。好了,现在让我们言归正传,实现我们自己的redux。1、最初,我们使用creat-react-app创建一个项目,删除src下多余的部分,只保留index.js,修改index.html的DOM结构:#index.html

我们在index.js中创建一个对象来存储和管理我们整个应用状态的数据,以及使用渲染函数在页面上渲染数据:constappState={head:{text:'我是头部',color:'red'},body:{text:'我是身体',color:'绿色'}}functionrenderHead(state){consthead=document.getElementById('head')head.innerText=state.head.text;head.style.color=state.head.color;}functionrenderBody(state){constbody=document.getElementById('body')body.innerText=state.body.text;body.style.color=state.body.color;}functionrenderApp(state){renderHead(state);renderBody(state);}renderApp(appState);这时,运行代码,打开页面,我们可以看到head中已经出现了红色字体“Iamthehead”,body中出现了绿色字体“Iamthebody”。如果我们将head和body视为根中的两个组件,那么我们就实现了全局唯一状态。这个状态是全局共享的,可以在任何地方调用。我们可以修改头部的渲染函数看看效果:文本;head.style.color=state.head.color;state.body.text='我是头部修饰的body';}我们看到在头部渲染函数中,我们不仅可以使用body属性的值,还可以改变他的值。这种方式存在一个严重的问题,因为状态是全局共享的,一旦某个地方状态的值发生变化,所有使用这个值的组件都会受到影响,而且这种变化是不可预测的,显然对于我们的代码调试的代码增加了难度,这样的结果是我们不想看到的!2.dispatch现在好像有一个矛盾摆在我们面前:我们需要数据共享,但是任意修改共享数据会出现意想不到的问题!为了解决这个矛盾,我们需要一个专门管理共享数据状态的管家。对共享数据的任何操作都必须通过他来完成。这样就避免了随意修改共享数据带来的意想不到的危害!我们重新定义了一个函数,并使用这个函数作为我们的管家来管理我们的共享数据:':state.body.text=action.textbreakdefault:break}}我们重新修改head的渲染函数:functionrenderHead(state){consthead=document.getElementById('head')head.innerText=state.head.text+'--'+state.body.text;head.style.color=state.head.color;dispatch(state,{type:'BODY_TEXT',text:'我是头部通过调用dispatch修改body'})}dispatch函数接收两个参数,一个是要修改的状态,一个是修改后的值.这时候,虽然我们还是修改了state,但是通过dispatch函数,我们让这个变化变得可控,因为任何改变state的行为,我们都可以在dispatch中找到变化的源头。这样一来,我们似乎就解决了之前的矛盾。我们创建了一个全球共享数据,并严格控制任何更改此数据的行为。然而,在一个文件中,我们不仅需要保存状态,还要维护管家函数dispatch。随着应用程序变得越来越复杂,这个文件将不可避免地变得冗长复杂,并且难以维护。现在,我们将state和dispatch分开:用一个文件单独保存state,另一个文件保存dispatch、changeState中修改state的比较关系,最后用另一个文件将它们组合起来生成一个全局唯一的store。不仅让单个文件更加精简,而且在其他应用中,我们也可以方便的复用我们的一套方法。我们只需要传入不同应用的状态,修改状态对应的逻辑stateChange,就可以放心的通过调用dispatch方法对数据进行各种操作:详见前端手写答案面试题#改变我们的目录结构,增加一个redux文件夹+src++redux---state.js//storeapplicationdatastate---storeChange.js//维护一套修改store的逻辑,只负责计算,并返回一个新的store---createStore.js//结合state和stateChange创建一个store,方便任何应用程序引用--index.js##修改了每个文件#state.js--globalstateexportconststate={head:{text:'我是头',color:'red'},body:{text:'我是身体',color:'green'}}#storeChange.js--只负责计算,修改storeexportconststoreChange=(store,action)=>{switch(action.type){case'HEAD_COLOR':store.head.color=action.colorbreakcase'BODY_TEXT':store.body.text=action.textbreakdefault:break}}#createStore.js--创建全局storeexportconstcreateStore=(state,storeChange)=>{conststore=状态||{};constdispatch=(action)=>storeChange(store,action);return{store,dispatch}}#index.jsimport{state}from'./redux/state.js';import{storeChange}from'./redux/storeChange.js';import{createStore}from'./redux/createStore.js';const{store,dispatch}=createStore(state,storeChange)functionrenderHead(state){consthead=document.getElementById('head')head.innerText=state.text;}head.style.color=state.color;}functionrenderBody(state){constbody=document.getElementById('body')body.innerText=state.text;body.style.color=state.color;}functionrenderApp(store){renderHead(store.head);renderBody(store.body);}//首先渲染renderApp(store);通过上面的文件拆分,我们可以看到,不仅让单个文件更加精简,也让文件的功能更加清晰:在state中,我们只在storeChange中保存我们共享的数据,我们维护相应的逻辑改变store计算一个新的store在createStore中,我们在index.js中创建store,我们只需要关心对应的业务逻辑3.subscribe一切看起来是那么美好,但是当我们调用dispatch修改store之后第一次渲染,我们发现虽然数据已经改变了,但是页面并没有刷新,只是在dispatch改变数据后,再次调用renderApp()刷新页面//先renderrenderApp(store);dispatch({type:'BODY_TEXT',text:'我调用dispatch修改body'})//修改数据后,页面不会自动刷新renderApp(store);//再次调用renderApp刷新页面,显然不能达到我们的预期。我们不希望每次数据更改时都手动刷新页面。如果我们能改变数据,自动刷新页面,当然再好不过了!直接在dispatch中写renderApp显然是不合适的,这样我们的createStore就失去了通用性。我们可以在createStore中添加一个集合数组,收集dispatch调用后需要执行的方法,然后循环执行,从而保证createStore的通用性:#createStoreexportconstcreateStore=(state,storeChange)=>{const监听器=[];conststore=状态||{};constsubscribe=(listen)=>listeners.push(listen);constdispatch=(action)=>{storeChange(store,action);listeners.forEach(item=>{item(store);})};return{store,dispatch,subscribe}}#index.js···const{store,dispatch,subscribe}=createStore(state,storeChange)······//添加监听器subscribe((store)=>renderApp(store));renderApp(store);dispatch({type:'BODY_TEXT',text:'我调用dispatch修改body'});这样,每次我们调用dispatch的时候,页面都会刷新。如果我们不想刷新页面,只想提醒一句,只需要更改添加的监听器即可:subscribe((store)=>alert('pagerefreshed'));renderApp(store);dispatch({type:'BODY_TEXT',text:'我调用dispatch修改body'});这样我们保证了createStore的通用性。4.优化至此,我们似乎已经达到了之前想要达到的效果:我们实现了一个全局的公共store,并且对这个store的修改进行了严格的控制,每次store的修改都通过dispatch,可以完成自动刷新页面。然而,显然这还不够。上面的代码还是有点简陋,存在严重的性能问题。虽然我们只是修改了body的文案,但是当页面重新渲染的时候,head也会重新渲染。那么,我们能否在页面渲染时对比新老店,感知哪些部分需要重新渲染,哪些部分不需要重新渲染呢?根据上面的思路,我们再修改一下我们的代码:#storeChange.jsexportconststoreChange=(store,action)=>{switch(action.type){case'HEAD_COLOR':return{...store,head:{...store.head,color:action.color}}case'BODY_TEXT':return{...store,body:{...store.body,text:action.text}}默认值:return{...store}}}#createStore.jsexportconstcreateStore=(state,storeChange)=>{constlisteners=[];让商店=状态||{};constsubscribe=(listen)=>listeners.push(listen);constdispatch=(action)=>{constnewStore=storeChange(store,action);listeners.forEach(item=>{item(newStore,store);})store=newStore;};return{store,dispatch,subscribe}}#index.jsimport{state}from'./redux/state.js';import{storeChange}从'./redux/storeChange.js';从'./redux/createStore.js'导入{createStore};const{store,dispatch,subscribe}=createStore(state,storeChange);functionrenderHead(state){console.日志('渲染头');consthead=document.getElementById('head')head.innerText=state.text;head.style.color=state.color;}functionrenderBody(state){console.log('renderbody');constbody=document.getElementById('body')body.innerText=state.text;body.style.color=state.color;}functionrenderApp(store,oldStore={}){if(store===oldStore)返回;store.head!==oldStore.head&&renderHead(store.head);store.body!==oldStore.body&&renderBody(store.body);console.log('renderapp',store,oldStore);}//第一次渲染subscribe((store,oldStore)=>renderApp(store,oldStore));渲染应用程序(商店);dispatch({type:'BODY_TEXT',text:'我是调用dispatch修改的body'});上面,我们修改了storeChange,让它不再直接修改原来的store,而是通过计算返回一个新的store我们还修改了cearteStore,使其接收storeChange返回的新store,dispatch修改数据,刷新页面后,将新store赋值给之前的store。当页面刷新时,我们对比newStore和oldStore,感知需要重新渲染的部分,完成一些性能优化。最后,通过简单的代码示例,我们对redux有了一个简单的了解。虽然代码还是有点粗糙,但是我们已经实现了redux的几个核心概念:应用中的所有状态都以对象树的形式存储在一个单独的store中。改变商店的唯一方法是触发一个动作,它是动作行为的抽象。