React.js 小书 Lesson2 - 前端组件化(一):从一个简单的例子讲起

不少课程一上来就给你们如何配置环境、怎么写 React.js 组件。可是本课程仍是但愿你们对问题的根源有一个更加深刻的了解,其实不少的库、框架都是解决相似的问题。只有咱们对这些库、框架解决的问题有深刻的了解和思考之后,咱们才能驾轻就熟地使用它们,而且有新的框架出来也不会太过迷茫;由于其实它们解决都是同一个问题。javascript

这两节课咱们来探讨一下是什么样的问题致使了咱们须要前端页面进行组件化,前端页面的组件化须要解决什么样的问题。后续课程咱们再来看看 React.js 是怎么解决这些问题的。html

因此这几节所讲的内容将和 React.js 的内容没有太大的关系,可是若是你能顺利了解这几节的内容,那么后面哪些对新手来讲很复杂的概念对你来讲就是很是天然的事。前端

一个简单的点赞功能

咱们会从一个简单的点赞功能讲起。 假设如今咱们须要实现一个点赞、取消点赞的功能。java

React.js 组件化图片

若是你对前端稍微有一点了解,你就顺手拈来:react

HTML:git

<body>
    <div class='wrapper'> <button class='like-btn'> <span class='like-text'>点赞</span> <span>👍</span> </button> </div> </body> 

为了模拟现实当中的实际状况,因此这里特地把这个 button 里面的 HTML 结构搞得稍微复杂一些。有了这个 HTML 结构,如今就给它加入一些 JavaScript 的行为:github

JavaScript:app

const button = document.querySelector('.like-btn') const buttonText = button.querySelector('.like-text') let isLiked = false button.addEventListener('click', () => { isLiked = !isLiked if (isLiked) { buttonText.innerHTML = '取消' } else { buttonText.innerHTML = '点赞' } }, false) 

功能和实现都很简单,按钮已经能够提供点赞和取消点赞的功能。这时候你的同事跑过来了,说他很喜欢你的按钮,他也想用你写的这个点赞功能。这时候问题就来了,你就会发现这种实现方式很致命:你的同事要把整个 button 和里面的结构复制过去,还有整段 JavaScript 代码也要复制过去。这样的实现方式没有任何可复用性。框架

结构复用

如今咱们来从新编写这个点赞功能,让它具有必定的可复用。此次咱们先写一个类,这个类有 render 方法,这个方法里面直接返回一个表示 HTML 结构的字符串:less

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() 

React.js 组件化图片

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

实现简单的组件化

你必定会发现,如今的按钮是死的,你点击它它根本不会有什么反应。由于根本没有往上面添加事件。可是问题来了,LikeButton 类里面是虽说有一个 button,可是这玩意根本就是在字符串里面的。你怎么能往一个字符串里面添加事件呢?DOM 事件的 API 只有 DOM 结构才能用。

咱们须要 DOM 结构,准确地来讲:咱们须要这个点赞功能的 HTML 字符串表示的 DOM 结构。假设咱们如今有一个函数 createDOMFromString ,你往这个函数传入 HTML 字符串,可是它会把相应的 DOM 元素返回给你。这个问题就能够解决了。

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

先不用管这个函数应该怎么实现,先知道它是干吗的。拿来用就好,这时候用它来改写一下 LikeButton 类:

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 插进去。

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

如今你点击这两个按钮,每一个按钮都会在控制台打印 click,说明事件绑定成功了。可是按钮上的文本仍是没有发生改变,只要稍微改动一下 LikeButton 的代码就能够完成完整的功能:

class LikeButton { constructor () { this.state = { isLiked: false } } changeLikeText () { const likeText = this.el.querySelector('.like-text') this.state.isLiked = !this.state.isLiked likeText.innerHTML = this.state.isLiked ? '取消' : '点赞' } render () { this.el = createDOMFromString(` <button class='like-button'> <span class='like-text'>点赞</span> <span>👍</span> </button> `) this.el.addEventListener('click', this.changeLikeText.bind(this), false) return this.el } } 

这里的代码稍微长了一些,可是仍是很好理解。只不过是在给 LikeButton 类添加了构造函数,这个构造函数会给每个 LikeButton 的实例添加一个对象 statestate里面保存了每一个按钮本身是否点赞的状态。还改写了原来的事件绑定函数:原来只打印 click,如今点击的按钮的时候会调用 changeLikeText 方法,这个方法会根据 this.state 的状态改变点赞按钮的文本。

如今这个组件的可复用性已经很不错了,你的同事们只要实例化一下而后插入到 DOM 里面去就行了。

下一节咱们继续优化这个例子,让它更加通用。


由于第三方评论工具备问题,对本章节有任何疑问的朋友能够移步到 React.js 小书的论坛 发帖,我会回答你们的疑问。

相关文章
相关标签/搜索