使用react什么的——我不是双向绑定
时间:2023-03-12 23:01:53
科技观察
我应该先做个什么例子?如果它是现代MVVM框架,它可能会用双向绑定来吸引您。React有双向绑定吗?没有,也算是有外挂了。但双向绑定与React的方式不同。React强调单向数据流。当然,即使是单向数据流也必须始终有一个数据源。如果数据来自页面本身的用户输入,那么效果相当于双向绑定。以下是实现此效果的方法。让我们设计一个登录场景。用户输入用户名后,会在问候语的地方显示用户名,如下图:作为一个警告,首先我想用这个小东西展示一下react的数据流工作方式+redux,所以这看起来像很多代码,肯定比一些MVVM框架双向绑定一对双花括号的代码多很多。但正如我之前所说,它们的方式并不相同。稍后你肯定会看到React模型的好处。在此,请耐心阅读这些看似冗长的代码。React和redux的很多重要思想都在这里开始涌现。先写出组件。为了简单起见,我们将整个登录页面作为一个组件,放在containers目录下。还记得前面提到的容器和组件目录吗?将组件放在容器目录中意味着组件必须与外界打交道。但首先,让我们将交互抛在脑后,编写一个简单的普通组件:div>
用户名:
密码 密码:
)}}exportdefaultLogin为了让我们写的东西能够显示出来,我们不得不更改模板代码,现在我们来修改src/index.js,里面原来的代码已经不用了,改成:importReactfrom'react';import{render}from'react-dom';import{Provider}from'react-redux';importconfigureStorefrom'../stores';importLoginfrom'../containers/Login';conststore=configureStore();render(
,document.getElementById('app'));设置环境时自动打开的浏览器页面还没关闭?保存代码后稍等片刻就可以看到我们制作的登陆页面了。目前该登录组件中问候语中显示的用户名与用户输入的用户名无关。如何连接它们?现在你看到了{this.props.username}你一定有一个数据模型。确实有这么一个东西,但是在redux中,这个数据模型非常壮观。整个应用只有一个数据模型,所以应该叫数据仓库。此存储库的代码位于stores/index.js中。代码很简单,就是用reducers和initialState两个参数创建了一个仓库。看刚才run.js中的代码,有一个组件叫Provider,使用了仓库。意思很明显:在provider组件内部,已经为我们提供了仓库的访问条件,也就是说我们的Login组件已经可以访问仓库了。如何访问它?我们需要将我们的组件连接到存储库。登录组件代码“exportdefaultLogin”的最后一行应更改为:functionmapStateToProps(state){return{}}exportdefaultconnect(mapStateToProps)(Login);connect是react-redux库提供的一个函数,它的作用是将组件连接到rudux仓库。注意在文件顶部添加一句“import{connect}from'react-redux'”。这里有一个函数mapStateToProps,它返回的对象是从仓库中取出的数据,具体的数据等我们写完reducer之后再添加。那么什么是减速器?我们考虑一下仓库里的数据要变了,怎么让它变呢?我们要给出一个规则,描述为:“当某个动作(action)发生时,仓库中的一部分数据必须相应改变。”我们称数据中因动作而改变的部分为状态,许多琐碎的状态组成了仓库数据,所以整个仓库其实就是一个大状态。在程序运行过程中,我们主要关心的是仓库的状态如何变化。如何?这取决于减速器。对于一个action,仓库中的一个或多个状态会发生变化,reducer就是指导状态如何变化。等等,这个动作是从哪里来的?具体来说,动作一般来源于用户的操作或者对网络请求的响应。在代码中,需要对动作进行规范。其实就是和reducer约定好,让它知道有action了。事实上,任何动作都可以表达,只要它是独一无二的。一般我们就用字符串,这样容易产生唯一性,也能表达意思,只是在使用的时候注意不要过火。我们定义一个用户输入用户名的动作:constINPUT_USERNAME='INPUT_USERNAME'为什么不直接使用字符串呢?为了避免低级错误,在定义了这个常量之后,在发起action的时候使用这个常量,reducer也是根据这个常量来区分action类型的。我们仅仅告诉reducer发生了“用户输入”动作是不够的,还要告诉reducer用户输入了什么。所以完整的动作必须是一个信息丰富的对象。为了方便,我们写了一个actiongenerator,它是一个函数:functioninputUsername(value){return{type:INPUT_USERNAME,value:value}}现在reducer可以获得足够的信息来指导状态的改变。reducer所要做的就是修改仓库中一个名为“用户名”的状态的值。由于state可以一个接一个嵌套,reducer也被设计成一个接一个嵌套。单个减速器是其父减速器的成员。其实reducer本身也是一个函数:functionusername(state='',action){switch(action.type){caseINPUT_USERNAME:returnaction.valuedefalut:returnstate}}reducer的函数名对应状态名,而该函数接受两个参数:*第一个是当前状态。如果是程序开始运行的时候,很可能没有当前状态,所以给一个默认值,这里是一个空字符串;第二个是前一个动作生成器生成的动作对象。reducer可以处理多个动作。目前我们只有一个,如果还有其他的,我们会直接添加案例分支。对于每一个action,reducer都必须返回一个新的state值,这个值可以根据action发送的信息根据业务需求生成。***请务必添加返回当前状态的默认情况。在redux中,任何action都会经过所有的reducer,所以对于一个reducer来说,在大多数情况下,action不能被它处理,最多返回当前状态值。觉得效率低下?😉别怕,白白走一遍分支,这对修改DOM之类的正事来说算不了什么。reducer是一个层层递进的树形结构,如何将它们组合在一起呢?Rudex提供了组合工具combineReducers。补充一下,我们已经编写了另一个名为password的reducer,将它们组合起来如下所示:combineReducers({username,password})注意combineReducers接收的参数是一个对象,而不是多个函数。上面的代码使用了es6的Shorthand。不难发现,上面的reducer和actiongenerator是非常死板的代码。以后我们会写很多这样的代码,会出现满是样板代码的情况,有点傻。所以我们把重复的东西尽可能多的提取出来,写一个reucergenerator和一个actiongenerator的generator://reducergenerator,为了以后使用方便,简称functioncr(initialState,handlers){returnfunctionreducernamedcreatereducer(state=initialState,action){if(handlers.hasOwnProperty(action.type)){returnhandlers[action.type](state,action);}else{returnstate;}}}//actiong生成器生成器,同理,命名为createactioncreator简写returnfunction(...args){letaction={type}argNames.forEach((arg,index)=>{action[argNames[index]]=args[index]})returnaction}这两个函数做的事情完全一样我们编写样板代码来做。详细解释一下:cr的两个参数:initialState为初始状态;handlers是由一堆函数组成的对象,每个函数的名字对应一个action类型,每个函数接受的参数和reducer一样,都是action和当前状态,返回值会被处理作为新的状态。默认情况下,我们不需要处理它。cac接受的第一个参数是动作的类型名,后面的参数是所有伴随数据的属性名。好吧,让我们理顺代码。对于目前模拟双向绑定的小功能,我们不需要记录密码的状态,但是我们也先写了,后面会用到。***先写动作。因为一般来说,只要想好你应用的功能,action就可以写了,action不依赖于其他东西。src/actions/login.js:从'../utils'导入{cac}因为类型名称现在都写在动作文件中,但也许最好将所有这些常量放在一个单独的文件中,这样可以借助es6语法避免重复。这里我们导出所有内容,动作类型名称reducer会用到,会用到actiongenerator组件,然后写reducer,当你想到应用的功能,接下来就是考虑背后的数据结构,reducer一写完,数据结构已确定('',{[INPUT_USERNAME](state,{value}){returnvalue}}),password:cr('',{[INPUT_PASSWORD](state,{value}){returnvalue}})})action文件的引用,路径中没有用../,这样写是因为actions是别名,代表actions目录的绝对路径,也就是webpack帮我们做的。当然你也可以定义自己的别名,只需要修改cfg/base.js,比如在resolve.alias对象中加入自己的工具集:“utils:srcPath+'/utils.js'”。rducer最终会被注册到store。这个过程已经写在src/storces/index.js中。可以看到里面的代码使用了../reducers这个文件(这是一个目录,真正的文件在index.js里面),所以我们也需要在这里注册新写的reducer。修改src/reducers/index.js:import{combineReducers}from'redux';importloginfrom'./login'construducers={login};module.exports=combineReducers(reducers);在reducers/index中,所有reducer也都通过combineReducers合并了,但现在我们只有一个子reducer:login。最后,是时候回到组件了。src/containers/Login.js现在应该修改成这样:.props.dispatch(inputUsername(evt.target.value))}inputPasswordHandler(evt){this.props.dispatch(inputPassword(evt.target.value))}render(){return(早上好,{this.props.username}
用户名:
密码 密码:
)}}functionmapStateToProps(state){return{username:state.login.username,password:state.login.password}}exportdefaultconnect(mapStateToProps)(登录);有几个变化:首先,前面提到,要将组件连接到仓库,需要使用connect。而既然我们已经确定了仓库中登录状态对应的数据接口,那么mapStateToProps返回的内容也就确定了。login状态下的这两个属性映射到组件的属性上,所以可以通过this.props.username访问仓库中的login.username。然后将更改事件处理添加到两个输入。当change事件被触发时,this.props.dispatch函数可以通知仓库有动作发生,仓库会调用所有reducer来处理这个事件。好了,小双向绑定功能就实现到这里了😓尝试一下。在MVVM框架中,只需要创建一个viewmodel,一对双大括号就可以搞定,为什么给redux加react这么麻烦?其实我是专门展示完整的redux+react开发流程。如果您只想在单个页面上使用此功能,请使用事件处理来更改组件的状态。那么redux为什么要引入这样一个过程呢?我觉得发展有几个特点:直观上,视野不同。让我们将它与MVVM进行比较。MVVM框架的愿景是局部的,而redux的愿景是全局的。MVVM对应一个控制器的模型,模型中的数据只能自己使用,模型之间的通信需要其他的数据传输方式。Redux(或者flux的模式)管理着一个大的数据仓库,所有细节的状态都可以随时从这个仓库中获取(是不是感觉云里雾里?),在开发单页应用的时候,这个优势会会特别明显。从编程语言的角度来看,redux+react的方式充分利用了函数式编程的优势。Redux(通量)强调单向数据流。单向数据流就像一条生产线。原材料经过各道工序依次加工,最终成为产品。在这个过程中,每个阶段都应避免外部因素影响原材料,否则会出现意想不到的产品(次品)。纯函数就像这个流水线中的步骤,让数据处理的过程简单明了。你找到了吗?纯函数是前面代码中的主力。减速器显然是纯函数。组件也是纯函数。请注意,我们的组件不直接受状态控制,而是有一个连接过程。状态映射到组件的属性。对于组件来说,它根本不知道状态是什么。这样我们的组件和reducer就非常独立了,非常容易测试,意思也很直白。吹嘘了这么多,以现在的简单代码想看出来也不容易。毕竟,这些代码没有实际意义。作为现代前端应用程序,它们甚至不是异步的。..然后在下一节中,我们将添加一些异步。