开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术:React 2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/76/detail/15745
React 2
内容介绍:
一、处理 setState
二、制作 React 项目
三、小结
四、课后答疑
一、处理 setState
把 Clock 变得比较好用,所以是用这种方式来进行封装。
同时刚才说到 State不能显示的这样去设:this.state.comment=’Hello’; .能够这样设只有一个地方,即在构造器,在其他地方都要用 setStat:因为只有 setState 才能去触发整个页面重绘的动作,而直接复制是不会重绘的,所以在这个地方要用到 seStatet 。在 setState 的方法里面,改变当前的时间的时候,一定要用这种 setState 方式来实现。
setState 本身也有可能是一步去更新的,比如想处理 setState 之后,counter 的这个状态马上就发生变化,就让他重绘一下。但这件事情能不能发生,这件事情被re-render接管了,可能很难去控制它,只能告诉他我想要怎么样。
那直接写: counter:this.state.counter+this.props.increment,是很难控制的。
主要是接管,也就是说虽然完成了 setState ,但react可能因为某种原因,比如说现在比较忙,或者因为其他的原因导致内存不够,就先不会,但最终还是会恢复一下。这对你我们说就不是个好现象,也就是说你会看到表有时候走的快,有时候走的慢;数字有时候闪的快,有时候闪的慢;有时候半天不闪,或者一闪就过去了。可能是这样,如果想要立即重绘就需要这样去写:把它不是写成一个表达式,或者直接把它换掉,而是在这里面写成箭头函数或者普通函数。在这个函数里面,
写他的状态发生了变化,或者是让它 return 新的发生变化的状态,只有这样写才能保证是立即做了一次刷新。刚才说的为什么不会马上执行,它出来的效果可能会是什么样子,比如说在构造器里面,设一个状态是这样的:this.state={post:[ ],comments:[ ] ; } 然后在 componentDidMount 的第一个 Mount 里面,又有一个操作在设置状态,就是说通过 posts 的操作抓取后面的东西,回来之后根据返回的值又设了一次状态,那设完这些状态之后,就有可能会做一次所谓的合并。即在调用它的时候,它就有可能去做合并,合并出来的是把他们状态就合并成当前这个状态。但是我们知道由于 state它是异步的去执行,再来看一下可能会出现什么情况。一开始 posts 是空,comment 是空,在 DidMount ,也就是它绘制出来,去做了到后台抓取的动作,
回来之后把post里面放进去了一点东西,就是你可以放出 response 回来的一些东西,或者在底下通过另外一个抓取把 comments 里面放进东西,
要注意的是因为他们是彼此独立的,是彼此独立的变量,他们的三个 state 都在设置,可能会带来的问题是一开始都是空的,然后分别只设置 post ,只设置 comments,然后就会告诉你在设置comments的时候,可能会导致 post 出问题。
因为他有两个变量,各设各的,互不干扰,分工明确,在 DidMount 里面又是异步执行的,所以就很难说到底谁先执行。也许在后面执行的看起来是在后面执行,但最后在异步执行的时候,会跑到前面去,导致可能存在一些潜在的风险。这个问题不用深究,因为大家现在在写代码中可能不会有什么大的问题,只是未来做一些复杂的程序的时候,会碰到问题:假如说在这个代码里,除了写 setState 在后面外,还想对 post 做操作。
理论上 post 因为在他之前执行了,所以有东西了,这时候我拿到comments 之后对 post 做操作是可以;但实际上会不会碰到这个问题很难说,因为进行了异步执行。大家写代码可能会遇上这个坑,但我觉得现在这个阶段大家可能不太会碰到。
接下来说刚才我们说的属性往下传, comments 属性可以传给 user infrom ,props 又传给armter 。
那么由此可引出 state能不能传?刚才讲了 state 的前提是它是私有的,因此就不应该
把 state 再传给其他东西,如果你真想把 state 往下传送,可以把当前的 state 当成另外一个 state ,就像刚才看到的 comments 里包含comments infrom ; state 里包含armter ,可以把他的
状态当成它的子构建的属性往下传,因为传下去的子构建里面它是 props,它的状态不会发生变化,这种传是可以的。那你想把它传下去,它会成为子的构建里面的状态,这可能就做到了。我估计大家也没有这个想法,就是把这个 state 给它当成是component的一个 state 的往下传。
应该大家不会有这个想法,如果你有这个想法,要注意只能当 props 传下去,然后在子的 component 里去设它的state,才能等于 props 里传进来的东西。
这是我们看到的创建三个 Clock出现的效果是这个样子,就在demo9里,就不进行演示了。从这里面大家可以看到,我们把 Clock 独立出来的好处是代码得到了复用。然后我们谈一谈 React元素上面的事件:事件和原始的 HTML 页面上的一些元素的事件概念是一样,就是在按钮上有个点击,可以在按钮上有鼠标滑过等的动作。在 React 里面唯一的一点是他用的是驼峰编码,驼峰编码就是每一个除第一个单词之外的每一个单词的字母大写,这一点和 HTML 是有点差异的。而且这点差异也容易写错,即在编写代码的时候很容易写错。要注意这个问题,其他的也没什么了。
那么在事件里面,还有一个就是原来的规范。
比如说我们在默认在 HTML 的页面里面,如果定义了一个超链接,这个超链接其实并不是想链到哪里去,而是只想做一个想要的动作。比如说 Clock 是一个形式上的超链接,但是你并不想让它链到哪里去,只是想在点击的时候在 console 里面记一条日志,想做这样的一个东西。也就是说要把默认的这个行为屏蔽掉,防止默认行为打开一个新网页,而且要出现这样的效果。这个事情如果放到 React 里,还要明确的写这一句话:e.preventDedfault ( ); 就是说处理事件的时候,这个事件要防止默认行为,然后再写预加行为是什么。另外从这段脚本可以看出来它的事件是什么意思,首先它是写的 onClick 是驼峰方式,是大写的,所以它是一个react的事件。那么这个脚本如果点击的话,再用这个函数,这个函数就在这里,把事件的默认行为屏蔽掉,然后在控制台上写一个log,整个的东西是作为这个函数,将来就作为一个标签出现。
举例:
从 Toggle 看出是写了一个切换的例子。是说构造进来之后,大家看到他没有使用 post ,只说设置了一个自己的一个状态,这个状态就是一个 is ToggleOn: true 的标志位,在说当前的这个切换的状态是开还是关,一开始的设计是 true ,然后底下又定义了一个一个函数,这个函数就是 handleClick 和当前这个对象绑定,就是和当前创建的 Toggle 对象绑定起来。这个函数在重新设置状态,设计成 is ToggleOn 这一个状态的取法, prevState 当前这个状态,要把它变成当前状态里面的这一个值的取法。,就是如果原来是 false ,要变成 true ,把前一个状态
中的is ToggleOn取下来。其中里面的 this 指的是当前在这里已经把这个函数和当前对象绑定了,所以 this 就是当前对象。这里的意思就是说这个函数一旦被调用,就会把 is ToggleOn的标志取消。底下的 Toggle 是怎么绘制的?它的绘制是说要画一个按钮,这个按钮里面一旦被点击就会去调 is handleClick ,is handleClick就立刻就到这来找当前绑定的这个方法。然后他就去调这个了,由于是调了 setState ,如果里面写的是箭头函数,我们刚才讲用函数表示的话,它会被同步更新掉,如果你直接写的话,它是异步,不一定能马上刷新。
因为你写了它但它同步的被更新掉,所以页面马上重绘,就会看到 button 上显示的是 {this.state .isToggleOn ? :’OFF’} :判断一下当前对象的 is ToggleOn 的值是 true 还是 false ,如果是 true 显示 ON , false 就显示 OFF ,这跟其他语言的三元操作是一样的,前面的表达式是如果是 true 就是选 ON ,即冒号前面的,否则选后面的。
整个类定义完,要做的事情就是在页面上绘制一个 Toggle ,把它绘制到 root 里。刚才有同学把上下两个 render 搞混了,才说写了两个版本。
其实不是,仔细看上面的 render 是在类里面,下面的render 是说要执行的脚本,他是执行底下这个脚本要render 回来找Toggle 的定义,然后在里面再去render 它里面的内容,根据 render 的结果和操作的结果去决定代码,整个过程下来是这样一个顺序。
举例:
这个例子非常简单,只有一个按钮,就是这段脚本,
刚才说一键注释,在windows上 Ctrl \ max 。它的效果就是这里的按钮,你按一下按钮就切换一下,切换成 OFF ,最后再按一下再切换成 ON ,就是这样来回切换,而且是你点完之后马上切换。就是刚才说调用箭头函数的方式在调 setState ,马上就可以去刷新。大体上 react 的东西就这么多。
来看一个例子,这个例子是在 react 的官网上给的,说我要展示这样一个东西。
先给大家看一下它的效果和想实现的功能:
它的功能就是会展示一些商品和它的价格,然后进行分类展示,比如运动商品和电子产品是不同的。如果选 Only show products in stock,红颜色就表示已经没有库存了,就不会显示了。
然后 search 是表示你要去写东西,写一个底下有的东西,比如说 ball ,那我每次写字符的时候他都会去判断,我一按一下它就会判断当前哪些有你,会把内容给刷新掉,把一些展示内容刷新掉。如果在 Only show products in stock状态之下搜索也是一样的,那些没有库存的东西都不会显示出来,就是要实现这样一个状态的代码。这个代码的内容是在前边填写的,内容就是
每个商品占一行,它是什么种类、价格是什么、是否具有库存,具有为 true 没有库存就是 false ,以及它是什么物品。物品 name 部分对应的就是这里显示的东西;价格 price 对应的就是相应显示的东西。 stocked: false 对应上边程序里的红颜色部分;Sporting、Electronics对应的是 Sporting Goods、Electronics;总之就是要显示这样一个页面。首先说有关内容的数字是写死的,要显示的内容是在前端写死,最终呈现成这个样子,还能去做只显示库存和 search 这样的动作。
二、制作 React 项目
制作 React 项目的时候,最重要的是要先要去考虑目标页面里面到底要有哪些Component,刚才说 Component实际上就是要把整个页面的内容进行切分,然后 Conponent 还可以一层套一层。
这就要仔细去看,还需要把代码分成不同的模块,然后它每一个模块就变成一个 Component ,多个Component构成一个更大的 Conponent 。
1.第一步:先去做划分。在当前这个页面里面可以怎么划分?
(1)所有东西都在一个橙黄色的框里面,我们就叫它可过滤的商品表(FilterableProductTable),即包含在整个框里面。
(2)第二部分是搜索条,搜索条里面还包含了显示只有库存的一个复选框。底下就是要展示的商品,所以整个就是 ProductTable ,即绿颜色的这块。但是这里面又分两个不同的东西,就是上面的是表头的name price,下面的就是行,但是行又分两种,一种是产品的种类行,就是蓝色的框只显示产品的种类,就是一个字符串。底下的是产品的行,就是红颜色框起来的六行,其中一行显示一个产品,产品有名字和价格。就是我们从前面的内容出发,要组装出这个内容来。
分析一下这个页面,它应该包含了这样的一些构件。然后再看后面 ProductCategoryRow、ProductRow 这两个构件是放到这个 Table里了,然后这个 Table 和 Search Bar就是放到最大的 Product Table 里,于是就形成了这样的一个分解。所以其实第一步我们是要去做这个分解,这个分解的依据是按照他们的功能,各自是在干什么;按照覆用的力度,力度是怎么样的?
就像刚才看的产品栏应该有六个都可以覆用同一个力度,而产品目录讲的实际上是一样,。既然这样的话,把他抽象出来,变成一个单个的空间。所以第一步是要做这样的动作,这个动作实际上它不只是 React 需要做这样的处理,就是做其他的语言做面向对象的设计,也要做这样处理。所以这实际上这就是一个问题的抽象。就是怎么把框架抽象出来,变成这么多的内容,怎么把它抽象成这几类东西,然后我们就出来了这些构件。所以这个东西非常重要,大家在做其他的页面设计中也是一样。现在画 HTML 二页面,你说到底有没有用,你必须要有这样的一个骨架出来,才能够去确定将来怎么去做 React 的工作。可以先把这个页面做成死的静态页面,然后把它搬到你的 React 代码里。刚才我们看到 React 的代码, render 里面还是那些 HTML 的标签,不断的用 React 就把他实现掉。所以第一步是有了这样的东西,有了这样的内容之后,就一个一个来设计。
2.
首先看这个产品的这个种类一栏,也就是前页的 Sporting Goods、Electronics 一栏。他需要转化吗?反正你会告诉我它的种类是什么,并放到 props 里面传递给我,即会在构造的时候传递给我。我就去转化,就是一个 tr 的标签,就是一行里面的<tr colSpan=’’2’’>。因为它是粗线,所以用表头的方式来画空两格,即在边缘 spond 有多少;把 category 的内容就写到这里,所以就是要做这么个东西。这里面我们可以看到,其实因为是我给了一个 category ,所以直接把它写出来就可以,而且在刚才的例子里面,我们看到他的状态不发生变化,所以在这里先不考虑 state ,这样就处理了有关 category 的这一行;然后我们再看产品,产品这行稍微复杂一点,产品行里面要考虑它到底是不是红颜色的,也就是说没要在有库存的情况下显得这种红颜色。所以在这里面要说产品行是怎么绘制的,要告诉我这是什么产品,并看一下这个产品的库存,如果有就是 true 就直接显示它的名字,如果没有,就要把它变成红颜色。以下的内容
<span style ={{color:’red’}}>
{product .name }
</span>
整个变成 name ;或者是span 标签要么是红颜色,要么把 product name 嵌进去。然后不管是哪一个,最后就是跨表格的一行加粗,所以就是 Tabledate 。然后有两列,第一列就是name,第二列就是product.price 。很显然在创建它的时候,必须要有一个product,其中包括 name 属性和price 属性。这两部分在画的时候是放到一行里面,是一个 td ,这就是我们在上边表格中看到的这个效果。再看这个产品表,就是把刚才看到的这个蓝颜色和黄颜色的东西嵌进去,并且上面还是 name 和 price 合在一起的绿颜色。
产品表他是由若干行构成,可以看到这个原始数据并没有专门的把 category 拿到外面。
是在每一个产品里面都写他是哪个 category 。我们要知道它是哪个 category ,要把它分别显示在这里。所以用了一个参数来做处理,这个参数叫last Category。已知 props 给了一组 products,对里面的每一个 product 都进行处理:先看看他的 category 是不是等于last。一开始为空,所以第一个产品进来一定不等于,不等于的话,就在 rows 里面push 进去一个元素,因为这是个数组。push进去一个 ProductCategoryRow,就是我们刚才看到的,给你一个 category 就加粗显示的。创建一个这个东西,刚才我们看到在进来的时候,他引用这个 category ,所以这个 category 一定是传递给他的。所以在这点我们看到在创建它的时候要传递给他一个 category={product.category}的属性。这个属性从哪来的?就是你当前 product category ,然后给了他一个 key ,这个 key 虽然在刚才这个地方没有用到,但是在后面就可能会用到的,所以设了一个 key ,大家先记住在这里给了一个 key 。
因为第一次进来这个事情就满足了,所以就换了这个第二次。除了第一次之外,我们再看第二次再进来,如果还是同一种商品,因为在这个地方我们看到对于每个产品做完之后, lastcategory =product category ,如果第二个产品跟前一个产品 category 不一样,就不做这一部分。所以只有碰到不同的 category 的时候,它才会往里面放新的元素 category row 进去,否则的话就不再放了。不管放不放,紧接着要做的事情就是如果放,只是说这是一个新的种类、产品,只是把种类放了,产品还没放,如果不放就意味着跟前一个种类一样,即产品本身要放的,所以在 rows. 里面又 push 进去一个 product row ,整个是在这个循环里的,这个 row 里面就会放进去一个 product ,然后给了一个 key ,key 里面就是product name ,然后这样处理完之后你就会看到一个 rows 里面都包含了若干行,有的行是种类行,有的行是产品行。然后在这里处理完,整个循环走完之后,rows就带东西了,render 就该返回了。画一个表,表头就是 name 和 price ,就是我们一开始看到的这 name和price,底下就是这个表的 body,就是这些 rows 。即 product row 或者是product category row,就把它们画出来了。
所以这就是 product table 给它画出来了。也就是底下的绿颜色最重要的部分给这个画上。然后再看 SearchBar 就是搜索这一部分,也就是刚才看到的上面这个蓝颜色这一块,还有一个输入文本框,有一个复选框,那在这里他要做的事情就是要有一个 form ,把这个复选框的输入和这个文本的输入放到了一个 form 。
然后上面这个text,我们看到是在搜索这个地方是有一个占位符。当你什么都不填的时候,他写了个Search ...。就是从 placeholder=’’Search...’’/这里来的,即占位符。复选框是显示的内容是only show products in stock。这就是我们看到的内容,一开始它是空的,所以没有东西。 SearchBar放在这里,我但们还没有写到它的功能。先把页面给它画出来,不写功能;
再看这个最大的 FilterableProductTable 结构,里面实际上就是我们刚才说的每一个里面就是单根的 </div>扩展放 SearchBar 、ProductTable。ProductTable里面要把 product给他,所以他的products就等于传递进来的 Products 。底下我们可以看到在绘制这个表的时候,会给一个product 传给他。Products是从刚刚定义的写死的常数项里面拿到的,然后把它给绘制到整个页面的某个标
签里面,比如页面里有一个 container 的地方就给它换进去,这就是这个版本。我们看到的,就是我给大家写的 table11的内容,filter1这个例子.这个例子出来之后,就是你现在看到的就是反馈界面。这个界面目前是不能交互的,因为没有写具体的东西。那为什么要先讲11这个版本?现在讲这个版本的目的是要告诉你怎么去设计这些 conponent 。从这里可以看到,
我们是从页面的角度去看到怎么把 component 识别成一块一块的内容。
刚才讲了要考虑到它的覆用、考虑到它的力度,考虑组合的关系,所以设计出来不同的 component ,然后把他们组合呈现出我们想要的东西。
那刚才所有的东西都没有讲到 state ,全部都是 props 在进行操作。如果想通过复选框去改变里面呈现的内容,是需要用到state 的,关键应该怎么去设计。
首先 state 是一种数据,所以要考虑在整个例子里面有哪些数据。第一个是原始的,没有做任何过滤的 products 的列表;第二是用户输入的搜索字符串,即要搜什么;第三个就是 check box选了还是没选。很显然, The search text the user has entered和 The value of the checkbos 这两个东西和用户的操作有关,所以肯定是状态。再看 Thefiltered list pf products ,这是指原始的 product 经过过滤之后产生的结果。那过滤后的 product 至少在刚才的例子里面看到了有四种数据,所以就需要一个一个去看他们哪一些属于 props 是不能修改的,哪一些属于 states ,
是可以修改的状态。怎么去理解这个问题?就要问自己三个问题,下面就讲到了:
(1)这个东西是不是从副component传递进来?通过 props 传递的话,如果是父亲传给你的,那就是刚才讲的函数的自变量是不能修改,所以它不应该成为状态。很显然我们刚才看到product 是在外面 render读了以后通过 props 传递进去的,他不应该当成一个状态进行管理。其实本身也很好理解,就是要呈现哪些产品跟用户的交互是没有关系,产品本身就放在那,不能说有某一个用户他操作了一下,产品就多了一个,不应该是这样。
(2)这个数据传递进来之后,或者说看控件里面定义之后,会不会修改?如果说不会被修改,那他就不应该被当作是状态。不会被修改就意味着创建了多个控件的实例,他们的值都是一样的,
彼此之间没有任何差异。那为什么要花精力去维护它?所谓 state 的问题,就跟我们讲的CDsation 是类似。因为不同的实例各自的取值是不一样的,所以才会去保存,比如像刚才的 clock ,
如果要显示不同的地区的时间,那日期就会不一样,时间也会不一样,所以他们应该是各自不同的 state 。如果大家都一样,那放这个 state 就没有任何意义。所以如果不随时间发生变化,那就不会是一个state。
再看,正如面向对象设计一样,属性会不会是一种计算属性,也就是它其实是可以靠其他的状态或者属性计算得来。如果是,就没有必要专门把它表示成一个状态,通过计算得到它就可以了。举个例子,比如书的价格都有,还要不要存书的平均价格?书的平均价格是靠其他书的价格计算出来的,就没有必要把它当一个状态。经过这样的分析,会发现原始的产品列表,用户的操作不随你创建多少个产品表而发生变化,所以它不是状态。下面的这 value of the checkbox 和 filtered list of products 是靠用户的点击来产生,而且它的状态和值会发生变化,所以它俩就是状态。被过滤掉的这个产品,实际上只是没有显示而已,但它本身的列表内容是可以通过 search text the user has entered 和value of the checkbox的状态以及原始的数据去计算出来可导,而不用专门去进行一个维护,所以它不是状态,综合上面的分析就得到这样的结论。所以把它分开来去讲,React 里的内容。
之所以要分开讲,就想跟大家强调前面是在说怎么去设计 component :把一个页面划分成很多不同的 component ,后面是在说哪些东西算状态,一再强调的是 props 是不能改的, state 是可以改。每一个不同的 state的实例都可以不一样,可以人为的去修改。
定义好这些之后,再来看怎么去实现刚才的过滤功能。状态有了,即点击的这个动作和输入的,Search 的文本和选择复选框的值。现在要考虑一个问题,这两个状态应该给谁,放在哪里去维护?刚才设计出来的东西有很多,有不同的框,即不同的一个 component 。现在的问题是 Search 的值和复选框的状态,应该是蓝颜色控件的状态,还是黄颜色的空间的状态。即它应该是 SearchBar的状态,还是整个 FilterableProductTable 的状态。虽然识别出来数据里面应该有两个状态,但它到底应该是谁的状态,它应该放到哪里?有可能有多个控在用,这种情况下,就要把它放到高一层,比如说如果说底下的控件 ProductTable 也想用到 SearchB或者 product 的状态,那你把它只放到 SearchBar里就显得不合适,也不是放到底下的 ProductTable里。应该再往外放一层,放到公共的副节点上,也就是整个黄颜色 FilterableProductTable里。实际上现在我们讲的这些东西和 react 本身没关系,而是和你面向对象程序设计有关系。其实你抛开 react ,抛开 JPA 的语言去想,Java设计或者 C+设计也是这样的。这些东西应该放到哪里去?只要有公共的,那就应该往上放,否则的话就应该把它尽量的往低放,这样的话让他只在它有效的生存周期内去生存。
那现在我们来看,现在有 product、table,有 SearchBar 他们两个里面的 ProductTable 要去过滤产品列表。就是看哪一些不显示了,或者说哪一些是 SearchBar 需要去搜索的东西。它需要用到 SearchBar 里面的内容,所以你把那个搜索的文本和复选框的值就放到 SearchBar那么 ProductTable就看不到了。所以在这种情况下就不太合适,我们认为这两个值这个状态应该放到他们的副节点,就是 FilterablePrductTable 里才合适。
所以现在说在 FilterablePrductTable 里面去放这两个东西。我在 FilterablePrductTable 里面放了之后,一开始可以是设计他们的初始值,就FilterablePrductTable =空,然后库存选中是false,把它放到里面,在它的副节点的构造器里去赋一个值。这两个值在底下的 inStockOnly 、ProductTable 里面都会用到,所以就把它们当成 props 传递过去。之后,用户在输入或者是做出单选的时候,就在这个表里面去用数据去考虑怎么去过滤,然后根据用户在 SearchBar 里面的动作去改写这两个值。
有了这样的一个分析之后,现在继续来改写这个代码。你现在看到 ProductTable 要获取从QuterableProduct 传递进来的 product props 里面的过滤的文本和是和只显示库存产品的这两个值。然后在刚才讲的对每一个产品进行操作的时候,要考虑过滤的文本,比如说输入 ball ,在这个 product的name里面能不能indexOf 找准字符串出现的位置。也就是说fiterText如果是name的子字符串,它indexOf会返回子字符串起始索引的位置,如果不是它就会返回-1。如果返回-1,就意味着产品不能放进去,要被过滤掉,它不包含在 Search 的范围内,所以就直接 return 了,啥也不做。然后再看,如果库存部分已经设置了只显示带库存的,并且当前产品没有库存量,也就是说它库存是没有即为 false,所以不显示,就直接返回。那么,即使满足了过滤的要求,但是如果用户设过否是显示库存,而且确实没库存,那也不放到这显示内容,大概就是这样一个逻辑。要增加这样的逻辑,也就是说在画 ProductTable 产品就不在刚才我们看到的 rows 的数组里。刚才的代码实际上是都补在 product 里面了,一开始去进行操作,所以这样的行就过滤掉了。
然后我们再看 SearchBar 页面要干什么, SearchBar 里面给了它过滤的文本和内容。我们要做的事情就是把这个过滤的文本显示到输入的文本框里面,写什么就显示成什么。然后选中的部分去改变在复选框上有没有选择的?即选中没选中的值就是等于要传递的、显示只有库存的这个值,即 false 还是 true ,取决于传递下来的值。再看在 FilterablePrductTable 里面,现在多了两个东西要有状态,就是过滤文本和显示的复选框,这两个都是有一个初始值,一个是空,一个是false。整个系统就是 就是在画SearchBar,ProductTable,他把这两个自己的属性的这东西,以及创建的时候传递给它的 Products 。这个 Product分别作 props 传递给 SearchBar 和 ProductTable。这样的话,就相当于刚才我们说的这个动作。再往下要看到实际上是 FilterablePrductTable 和 ProductTable把值传递给了SearchBar。按道理应该是在 SearchBar 里面输入东西或者做了选择之后,把他的状态拿回来,并去更新它里面的这两个状态。就是 filterText 和 inStockOnly 这两个状态。所以要定义 onChange 事件,于是在 SearchBar 里面,首先构造器里面去接收 filterbaleProductTable 传递给他的 Product ,也就是说filterText 和 inStockOnly 就在这里传递给他。
进来之后,定义两个函数,一个是 FiltetTextChange ,一个是 InStockChange两个函数。这两个函数定义完了之后,要跟当前这个对象绑定,当前创建出来的 SearchBar 绑定。绑定完说我在还是返回一个 form ,但是我多加一个东西。首先它的值等于这个传递进来的 filterText ,因为它是一个输入文本框,所以我在里面可以输入数据。一旦输入数据,就相当于触发了on Change这个方法,一旦触发 onChange方法,就要去调这个对象的属性,定义的就是这个方法。这个方法到这里,就会看到传递下这个事件,要去调 onFilter ,在属性上面去调 onFilter 方法。 props 既可以传属性,也可以传方法进来,让 onFilter 调进来的。我们看到 onFilterTextChange 传递给这个方法的值就是产生实践对象的值。是什么呢?
产生事件的对象就是输入框,输入框里面的值就是输进的东西,比如 ball ,他把所有的值传递给了函数,然后函数就处理了。那这个函数显然是在刚才看到的QuterableProductTable 里定义做的 FiltetTextChange 的方法。他要去定义刚才 SearchBar 的时候,传递的有过滤的文本是不是要库存,然后传递了两个函数对象,那他们俩的属性就是刚才看到这边的 FiltetTextChange 。On InStockChange实际上就是当前对象的 FiltetTextChange 的函数。于是到 handleFilterTextChange 这行的函数,这个函数拿到传递进来的 FilterText 太死了,即传递进来的 ball。
拿到ball之后, setState 的是当前 FilterProductTable 里面的一些 filterText 替换成了另一个 filterText。注意,这里 setState会调 render 重新绘制一遍。刚才我们说了,在重绘制的时候他又绘制 SearchBar的时候,就把 filterText 传递进去,此时的 filterText 就是新的 filterText 了。那这边的 props 再拿到的 filterText 都是新的 filterText 。
所以你看这个地方绕了一个圈,是说在这个Bar里面产生一个状态,这个状态初始之后设成空,传递给 SearchBar , SearchBar 拿来之后赋给in put ,然后有用户在 input 里面写东西的时候,就把写进去的东西回调当时让回调的函数;然后把 future texts状态更新一下,更新成刚才输入的东西,再进行去重绘制。重绘制的时候,把新的 filterText 传递给了 SearchBar , SearchBar 传到这里之后,就可以看到的过滤的文本就是新的文本,就是输入的 ball。所以要注意就是,看起来好像是在 SearchBar 的输入文本里输入,但是真正 ball 这个词要体现在看到的this.handleFilterTextChange 上,是做了这样一圈的处理。先写入,然后触发事件传递给他的负控件FilterProductTable ,然后 productTable 更改状态后,拿他的状态再重新交给 SearchBar 作为它的 props ,props 拿到之后才去更新了,Search 部分这块才是变成了 ball 。所以是绕了这样一个圈过来。同样的道理,底下这我就不说了,也是绕了一个圈这样过来的。
看一下这个完整的这个代码,这边是 producttwo 里面的东西。上面的部分没什么变化,即上面呈现的东西没什么变化,就是在productTable 这里刚才只讲了一方面,就是 SearchBar 的作用是什么?一定要记住。是在这里面写东西,能把它反映到它的副节点,也就是这一个 FilterProductTable 为了跟刚才这里叫做二,能反映他的状态到text和 instockOnly 这两个状态里面去。用户通过 SearchBar 里的这两个东西的输入,能够反映到这个状态里。这个状态就是每一次他的状态发生变化时,除了刷新 Search之外,他还刷新了 productTable ,就把这个新的 FilterableTable 和 instockOnly 这两个东西传递给了 ProductTable 。现在再来看 ProductTable ,跟刚才的那个写的 ProductTable 也有点区别。首先从它的副节点传递进来的,通过 props 传递进来的 FilterText 和 inStockOnly 里就获取到了这两个东西,然后它现在在显示内容里增加显示的产品目录或者是产品的时候,就增加了对 FilterText 和Product 的过滤。这两个条件只要有一个满足就不添加,也就是说如果有过滤文本,但过滤文本不在里面没有,或者说过滤文本是空。如果是空的话,那它返回一定不是-1,因为这个内容的返回包含空的。就是如果 Filter 是空的,就什么都没有,就不会返回-1的,而是会就会增加一条,然后stock的话,如果你选中的这个产品里面确实有 stocked,也不会直接返回产生抵押东西。
if (product.name.indexOf(filterText)===-1){
return;
if (inStockOnly &&!product.stocked){
return;。
看一下这个过程,因为在productTable里面需要用到 filterText和 onstockOnly 。这两个值是在 SearchBar 里面让你输入 Search 没有库存的两个值。这两个值的状态要能够反映到Product里面,把这个状态变成是他们的副节点, FilterableProductTable 的State。然后让 SearchBar 这边去通过回调的方式把它在控件里面输入的值带回到 FilterableProduct 里面。一旦状态发生变化, SearchBar 里面的状态通过回调函数,改变了 FilterableProducTable 的状态。这边不但要重绘 SearchBar ,还要重绘 ProductTable 。在重绘 ProductTable 的时候,就把刚才 FilterText 和onstockOnly 这两个值就带了进来。所以讲来讲去,其实这么复杂的原因就是因为这个属性在两边都要用,而且还必须属性。因为它的状态和值在发生变化,把它当成 SearchBar 的属性,在 ProductTable里就读不到了,因为我们刚才说了 state就是一个私有的东西,只在当前的状态的有效。那你如果放到 SearchBar 里,无论在前台怎么改写,状态传不过来,只能找他们的副节点。
而副节点像中介一样,他会把这两个东西传递给以下的两个子节点,然后子节点其中有一个会改写这两个状态,会通过回调函数处理定义的回调函数给它传递回来,刷新他的状态。刷新状态的同时,就通知另外一个子节点说我要去新一下。这是它完整的过程,现在再来看这个例子就会看到它有刚才的这个效果。
三、小结
这个例子它比较复杂,我们只讲了三个方面:
1. 第一个方面是怎么去刷新、分割这个UI,然后把它变成不同的component 的,怎么样组合出来现在看到的完整页面?分割 conponent 的依据,就是你觉得你要赋用东西,即便不赋用,也会看到从功能上来说,较为单一的话就给你封装在一起,也就是我们在人工上面讲的,内部是比他的联系比较紧密,也就是所谓的结耦合;然后互相之间功能相对比较独立,以这种方式在划分页面,整个页面全部都是靠component的构成的。也就是说在这个页面里面,基本上像一个全面对象一样,所有东西全部都是 lay ,或者说全部都是 conponent 的,然后是靠 conponent 的堆叠,有的conponent的里面可以包含其他conponent的堆叠产生了完整的界面。 conponent 就是在做页面代码的封装,所以它的组织、结构就比较好,几乎没有重复代码。
2. 第二部分是在讲,在 react的时候数据到底哪一些是 state ,哪一些是props ,他们互相之间是什么关系。即识别出整个页面里面或者整个系统里面有哪些数据之后,哪一些应该属于props ,哪一些是state,然后他们分别应该位于哪里?由于这种属性要影响到底下的部分,所以我们把它放到了他们的公共副节点上,而且应该是尽量低的一个公共副节点。
如果说在FilterableProductTable 外面还有一层,也不应该把它放在这里,尽管外面那层也是 SearchBar 和ProductTable 的副节点之一,就是祖先节点,但是你不应该放到那里。如果说这个状态在两个 conponent 里面在复用,大家都要用这个状态,应该找一个他俩的最低的公共副节点,把它放进去,作为那个节点的状态。
3. 第三部分是在讲通过例子讲两个compartment如果要有这种共享的状态,是怎么做交互的?既然这个状态被放到了副节点脸上,是怎么做交互,且两边是怎么互相影响?所以这个例子是reputer的这个 tallral 里面写的。当然我为了让他在这 idea里面的react 工程也能跑起来,适当做了一点改写。拿出来的话其实意思一样,为什么选了这么一个例子,我认为是有深意的,就是刚才讲的这几点,恰恰就是你在 react 的时候最重要的几点。语法上的细节,不知道你可以去查;库的一些用法、一些函数的地方可以查;但是本质上是说在设计一个 react 工程的时候,要使用的一些方法,或者是一些要注意的问题。
讲来讲去就是刚才描述了几个步骤:怎么去做页面 conponent 的分解和设计;怎么去做 props 和 state 之间的划分。然后在不同的控件之间,是如何来做进行交互的,才是它的精华。主要原因是它具体是有两个版本,所以这里面我也放了两个。大家可以看到实际上跑第二个的话,所有功能性都是具备了的。那除了刚才我讲的这些例子,实际上也就这些,其他都属于细节东西,用的都要到他官网上查了,基本上它主要东西就是这些。
那么我在工程里面还放了一个例子,是叫做 game.cn。我也是从网上找的一个例子,那么他几乎全面面向曲线的。给大看一下,他做一个就是一个所谓的小游戏。
这个过三关的小游戏。你在上面去操作,然后他就会告诉该怎么走,然后他告诉你谁赢了,写了这么个东西。这东西虽然很简单,但是它是另外一个例子,它也在说明刚才我们反复强调的东西:构件互相之间怎么交互、怎么操作?这个例子写的比刚才的要更容易理解一点,比较简单,但是我没有在给大家讲。我就把它放到这个工程里,如果你下那个包的话就可以看到。今天讲的东西在就在这个包里,记得下一下。
四、课后答疑
同学:我们做的图书馆主界面不用框架写可行吗?例如上次给我们展示的类似的界面,一登录进去下面一排排列着有哪些图书、哪些图片的界面,可以用自己的HTML 加 CSS 写出来,感觉效果也挺好的。
老师:你什么地方打算用这种框架键。
同学:在那个搜索功能的时候。
老师:主界面是最适合用 react 去写的。
同学:但是主界面我觉得就是单纯的用它网上一些比较好的 CSS 框架写出来效果已经不错了。
老师:它的重点不是为了好看,而是是为了能有一些交互。我举个简单例子。看一下那个例子的截图啊。就像这个例子里面,你的意思是这些书的页面不需要用 react 来做是吗?
同学:是的。
老师:其实这个页面恰恰你用 react 的比较合适,那未来如果库存发生变化之后,主页面要不要重做?比如说未来我们这些书是从库存、后台拿了,然后动态去生成这个页面;再一个就是说,在这里展示的时候可能只是一个图,有书名还有一些价格,如果说这这下面还有一个按钮。箭头按钮一点,详细简介就会呈现。有很多东西可能你用框架中就会更合适一点。
如果大家往后看,我在后面已经写了一个数据,从后台拿到的例子,我我已经写过前后台通的例子,就在第十次课里面。在上课我们要讲的那个例子的基础上从后台拿数据,所以你们看一看的话就会知道,你必须要前后台沟通,页面是从后台拿到数据是动态生成的,所以这个框架会更好一点。现在有同学提到 CSS 其实这些框架都带 CSS ,你用的就是框架的 CSS ,你说默认的这个相关页面不可能是现在大家看到这个效果,它里面是用的 CSS ,你自己写的CSS也可以往里套,如果你觉得你写的比这个好看一样可以替换他。