欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

React.js 小书 阅读笔记

程序员文章站 2022-07-05 17:00:43
...

仅供个人学习!本文作者:胡子大哈
本文原文:http://huziketang.com/books/react/blog/lesson4

参考了https://www.jianshu.com/p/bef1c1ee5a0e


React.js 是一个帮助你构建页面 UI 的库。React.js 将帮助我们将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。

React.js 也提供了一种非常高效的方式帮助我们做到了数据和组件显示形态之间的同步。

React.js 不是一个框架,它只是一个库。它只提供 UI (view)层面的解决方案。在实际的项目当中,它并不能解决我们所有的问题,需要结合其它的库,例如 Redux、React-router 等来协助提供完整的解决方法。


点赞功能:React.js 小书 阅读笔记

如果你的同事要把整个 button 和里面的结构复制过去,还有整段 JavaScript 代码也要复制过去。这样的实现方式没有任何可复用性。

如何有可复用性?

首先了解render:

render是渲染的意思。

res.render(file,option)是express中专门渲染视图用的,首先你要在你的app.js或者index.js中设置一下渲染引擎,比如html,jade,handlebars(我自己使用的),mustache等。然后将视图模板的文件位置放入file,将传入的模板数据放入option对象中,模板引擎就能自己渲染出视图。给你推荐一个npm模块,express-handlebars,能很快搭建一个项目,你实践过,就能明白res.render.

而渲染,就是这样一个过程,浏览器根据页面的html代码、css定义、javascript脚本的操作,在浏览器上按照一定的规范(传说中的 DOCTYPE )显示出相应的内容。

举个最简单的例子:

<b>这个是粗体显示</b>

浏览器根据上面的代码在浏览器上面显示出粗体的文字,这个过程就叫渲染

我们先写一个类,这个类有 render 方法,这个方法里面直接返回一个表示 HTML 结构的字符串:

class LikeButton
 { render () 
   { return `
   <button id='like-btn'> 
   <span class='like-text'>赞</span> 
   <span><!-- 由于系统问题,这里是一个点赞的符号,如上面示例中所示 --></span> 
   </button> ` } }

然后可以用这个类来构建不同的点赞功能的实例,然后把它们插到页面中。

const wrapper = document.querySelector('.wrapper') 
const likeButton1 = new LikeButton() 
wrapper.innerHTML = likeButton1.render() 
const likeButton2 = new LikeButton() 
wrapper.innerHTML += likeButton2.render()

这里非常暴力地使用了 innerHTML ,把两个按钮粗鲁地插入了 wrapper//包装 当中。虽然你可能会对这种实现方式非常不满意,但我们还是勉强了实现了结构的复用。我们后面再来优化它。


补充:

innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。

语法

tablerowObject.innerHTML=HTML

现在我们要给按钮添加事件,因此需要点赞功能的html字符串表示的DOM结构

假设我们现在有一个函数 createDOMFromString ,你往这个函数传入 HTML 字符串,但是它会把相应的 DOM 元素返回给你。

// ::String => ::Document 
const createDOMFromString = (domString) => { 
const div = document.createElement('div') 
div.innerHTML = domString 
return div }

向之前我们写的代码里加入它,得到:

class LikeButton { 
render () { 
this.el = createDOMFromString(` 
<button class='like-button'> 
<span class='like-text'>点赞</span> 
<span><!-- 由于系统问题,这里是一个点赞的符号,如上面示例中所示 --></span> 
</button> 
`) 
this.el.addEventListener('click', () => console.log('click'), false) 
return this.el } }

现在 render() 返回的不是一个 html 字符串了,而是一个由这个 html 字符串所生成的 DOM。在返回 DOM 元素之前会先给这个 DOM 元素上添加事件再返回。

因为现在 render 返回的是 DOM 元素,所以不能用 innerHTML 暴力地插入 wrapper。而是要用 DOM API 插进去。

补充:API通俗的来讲,就是人家提供给你的,让你可以直接使用的东西。


构造函数 constructor 是用于创建和初始化类中创建的一个对象的一种特殊方法。

语法

constructor([arguments]) { ... }

addEventListener() 方法用于向指定元素添加事件句柄。

element.addEventListener(eventfunctionuseCapture)

event:字符串,指定事件名,注意: 不要使用 "on" 前缀。 例如,使用 "click" ,而不是使用 "onclick"。 

 

useCapture

可选。布尔值,指定事件是否在捕获或冒泡阶段执行。

可能值:
  • true - 事件句柄在捕获阶段执行
  • false- 默认。事件句柄在冒泡阶段执行

事件的三个阶段:捕获阶段 目标阶段 冒泡阶段

1、捕获阶段:

事件从根节点流向目标节点,途中流经各个DOM节点,在各个节点上触发捕获事件,直到达到目标节点。

2、目标阶段 
事件到达目标节点时,就到了目标阶段,事件在目标节点上被触发

3、冒泡阶段 
事件在目标节点上触发后,不会终止,一层层向上冒,回溯到根节点。


bind()方法创建一个新的函数, 当这个新函数被调用时其this置为提供的值,其参数列表前几项置为创建时指定的参数序列。

const wrapper = document.querySelector('.wrapper') 
const likeButton1 = new LikeButton() 
wrapper.appendChild(likeButton1.render()) 
const likeButton2 = new LikeButton() 
wrapper.appendChild(likeButton2.render())
//appendChild() 方法向节点添加最后一个子节点。

可以进行一些改动是点击后文字发生变化

React.js 小书 阅读笔记

是在给 LikeButton 类添加了构造函数,这个构造函数会给每一个 LikeButton 的实例添加一个对象 statestate 里面保存了每个按钮自己是否点赞的状态。还改写了原来的事件绑定函数:原来只打印 click,现在点击的按钮的时候会调用 changeLikeText 方法,这个方法会根据 this.state 的状态改变点赞按钮的文本。


代码中混杂着对 DOM 的操作其实是一种不好的实践,手动管理数据和 DOM 之间的关系会导致代码可维护性变差、容易出错。那么如何优化?

状态改变: 构建新的 DOM 元素更新页面

解决方案:一旦状态发生改变,就重新调用 render 方法,构建一个新的 DOM 元素。这样做的好处是什么呢?好处就是你可以在 render 方法里面使用最新的 this.state 来构造不同 HTML 结构的字符串,并且通过这个字符串构造不同的 DOM 元素。页面就更新了!

更新后的代码:

React.js 小书 阅读笔记

具体改了的地方有:

  • 新增一个 setState 函数,这个函数接受一个对象作为参数;它会设置实例的 state,然后重新调用一下 render 方法。
  • 当用户点击按钮的时候, changeLikeText 会构建新的 state 对象,这个新的 state ,传入 setState 函数当中。
  • render 函数里面的 HTML 字符串会根据 this.state 不同而不同(这里是用了 ES6 的模版字符串,做这种事情很方便)。

也就是说,你只要调用 setState,组件就会重新渲染。我们顺利地消除了手动的 DOM 操作。


上面的改进不会有什么效果,因为你仔细看一下就会发现,其实重新渲染的 DOM 元素并没有插入到页面当中。所以在这个组件外面,你需要知道这个组件发生了改变,并且把新的 DOM 元素更新到页面当中。

重新修改一下 setState 方法:

setState (state) { 
const oldEl = this.el 
this.state = state 
this.el = this.render() 
if (this.onStateChange) this.onStateChange(oldEl, this.el) 
} ...
const likeButton = new LikeButton() 
wrapper.appendChild(likeButton.render()) // 第一次插入 DOM 元素 
likeButton.onStateChange = (oldEl, newEl) => 
  { wrapper.insertBefore(newEl, oldEl) // 插入新的元素 
    wrapper.removeChild(oldEl) // 删除旧的元素
  }

所以你可以自定义 onStateChange 的行为。这里做的事是,每当 setState 中构造完新的 DOM 元素以后,就会通过 onStateChange 告知外部插入新的 DOM 元素,然后删除旧的元素,页面就更新了。这里已经做到了进一步的优化了:现在不需要再手动更新页面了。

非一般的暴力,因为每次 setState 都重新构造、新增、删除 DOM 元素,会导致浏览器进行大量的重排,严重影响性能。不过没有关系,这种暴力行为可以被一种叫 Virtual-DOM 的策略规避掉,但这不是本文所讨论的范围。

补充:关于virtual-dom

变化这件事

数据是隐藏在页面底下,通过渲染展示给用户。同样的数据,按照不同的页面设计和实现,会以不同形式、样式的页面呈现出来。

Web 的早期,这些页面通常是静态的,页面内容不会变化。而如果数据发生了变化,通常需要重新请求页面,得到基于新的数据渲染出的新的页面。

为什么不把事情做得更极致些,只更新变化的数据对应的页面的内容呢?

怎么做呢?操作 DOM 呗。DOM 就是浏览器提供给开发者用于操作页面的模型嘛,直接通过脚本来调用 DOM 的各种接口就 OK 了。而且我们还有了像 jQuery 这样的棒棒的工具,操作 DOM 变得 so easy。

然而,页面越来越复杂,聪明的工程师们发现数据变化之后,老是需要手动编码去操作对应的 DOM 节点执行更新,有点烦于是各种框架出现了,可以简化这个过程。
开发者借助框架,监听数据的变更,在数据变更后更新对应的 DOM 节点。

再后来 React 出现了,它不仅不是 MVVM 框架,甚至连 MV* 框架都不是。可 React 带来了新的思路!

就是每次数据发生变化,就重新执行一次整体渲染。的确这样更简单,不用去琢磨到底是数据的哪一部分变化了,需要更新页面的哪一部分。但是坏处太明显,体验不好啊。而 React 给出了解决方案,就是 Virtual DOM。

Virtual DOM 概况来讲,就是在数据和真实 DOM 之间建立了一层缓冲。对于开发者而言,数据变化了就调用 React 的渲染方法,而 React 并不是直接得到新的 DOM 进行替换,而是先生成 Virtual DOM,与上一次渲染得到的 Virtual DOM 进行比对,在渲染得到的 Virtual DOM 上发现变化,然后将变化的地方更新到真实 DOM 上。

简单来说,React 在提供给开发者简单的开发模式的情况下,借助 Virtual DOM 实现了性能上的优化,以致于敢说自己“不慢”。

有一个不好的地方,如果我要重新另外做一个新组件,譬如说评论组件,那么里面的这些 setState 方法要重新写一遍,其实这些东西都可以抽出来,变成一个通用的模式。我们可以把这个通用模式抽离到一个类当中。