React小书(完整版)

这是⼀本关于 React.js 的⼩书。 因为⼯作中⼀直在使⽤ React.js,也⼀直以来想总结⼀下⾃⼰关于 React.js 的⼀些 知识、经验。于是把⼀些想法慢慢整理书写下来,做成⼀本开源、免费、专业、简单 的⼊⻔级别的⼩书,提供给社区。希望能够帮助到更多 React.js 刚⼊⻔朋友。 https://segmentfault.com/a/1190000016198363
展开查看详情

1.2018/8/27 React.js⼩书 React.js ⼩书 Github 关于作者 这是⼀本关于 React.js 的⼩书。 因为⼯作中⼀直在使⽤ React.js,也⼀直以来想总结⼀下⾃⼰关于 React.js 的⼀些 知识、经验。于是把⼀些想法慢慢整理书写下来,做成⼀本开源、免费、专业、简单 的⼊⻔级别的⼩书,提供给社区。希望能够帮助到更多 React.js 刚⼊⻔朋友。 由于⽔平有限,编写的过程难免会有诸多错误,也希望⼤家在看的过程中发现了问 题,可以在 Github 上给该项⽬发 Pull Request。衷⼼希望可以有更多的⼈参与到本 书的编写当中。 2017-06-09 更新:本书所有练习题已经在 ScriptOJ 上线,读者朋友们可以直接在线 进⾏练习。 本书介绍 本书为有⼀点前端基础的并且是 React.js 零基础的同学⽽作,帮助他们掌握 React.js 并且灵活地把 React.js 应⽤到实际项⽬当中。如果你有⼀定的 HTML、 CSS、JavaScript 基础并且希望学习 React.js,⽽⼜觉得 React.js 当中有些概念⽐ 难以接受和理解,希望能够从零开始学习,那么本书很适合你。但如果你已经对前端 已经⾮常熟悉并且⽤过不少的前端框架和相关的组件化技术,建议你直接看官⽹⽂ 档。 本书并不会⽂档式地包含所有知识点,只会提炼实战经验中基础的、重要的、频繁的 知识进⾏重点讲解,让你能⽤最少的精⼒深⼊了解实战中最需要的 React.js 知识和 套路,轻装上路。如果需要更多更全⾯的知识点,可以参看更多的官⽅⽂档或者其他 丰富的资料。 另外,本书全书采⽤ ECMAScript 2015,阅读之前请确保⾃⼰已经掌握了 ECMAScript 2015 的基本语法,否则阅读起来会⾮常困难。 本书初定分为三个阶段,每个阶段最后会有实战分析,把该阶段的知识点应⽤起来。 第⼀个阶段:希望能让读者掌握 React.js 的基本概念和基础知识。包括问题的根 源:前端组件的复⽤性问题、数据和视图的同步问题。了解清楚问题以后再了解 React.js 的基础知识,包括 JSX、事件监听、state、props、列表渲染等。看看 React.js 是怎么解决这些问题的。这个阶段结束以后,读者就可以可以运⽤ React.js 构建简单的⻚⾯功能。 http://huziketang.mangojuice.top/books/react/ 1/4

2.2018/8/27 React.js⼩书 第⼆个阶段:让读者更进⼀步了解 React.js,包括组件⽣命周期及其含义、state 和 props 的进阶概念、props 验证及其意义、组件组合进阶、如何和 DOM 打交道、并且 开始引⼊前端应⽤状态管理所存在的问题。 第三个阶段:让读者掌握 React.js 较为⾼级的概念,包括⾼阶组件、context。并且 通过引⼊ React-redux 来协助我们构建较为完整的前端应⽤,还会开始深⼊讨论前端 应⽤状态管理的问题;关于 React-router 也会有所提及。 ⽬录 第⼀个阶段 已完成 100% Lesson 1 - React.js 简介 Lesson 2 - 前端组件化(⼀):从⼀个简单的例⼦讲起 Lesson 3 - 前端组件化(⼆):优化 DOM 操作 Lesson 4 - 前端组件化(三):抽象出公共组件类 Lesson 5 - React.js 基本环境安装 Lesson 6 - 使⽤ JSX 描述 UI 信息 Lesson 7 - 组件的 render ⽅法 Lesson 8 - 组件的组合、嵌套和组件树 Lesson 9 - 事件监听 Lesson 10 - 组件的 state 和 setState Lesson 11 - 配置组件的 props Lesson 12 - state vs props Lesson 13 - 渲染列表数据 Lesson 14 - 实战分析:评论功能(⼀) Lesson 15 - 实战分析:评论功能(⼆) Lesson 16 - 实战分析:评论功能(三) 第⼆个阶段 已完成 100% Lesson 17 - 前端应⽤状态管理 —— 状态提升 Lesson 18 - 挂载阶段的组件⽣命周期(⼀) Lesson 19 - 挂载阶段的组件⽣命周期(⼆) Lesson 20 - 更新阶段的组件⽣命周期 Lesson 21 - ref 和 React.js 中的 DOM 操作 Lesson 22 - props.children 和容器类组件 Lesson 23 - dangerouslySetHTML 和 style 属性 Lesson 24 - PropTypes 和组件参数验证 Lesson 25 - 实战分析:评论功能(四) Lesson 26 - 实战分析:评论功能(五) Lesson 27 - 实战分析:评论功能(六) http://huziketang.mangojuice.top/books/react/ 2/4

3.2018/8/27 React.js⼩书 第三个阶段 已完成 100% Lesson 28 - ⾼阶组件(Higher-Order Components) Lesson 29 - React.js 的 context Lesson 30 - 动⼿实现 Redux(⼀):优雅地修改共享状态 Lesson 31 - 动⼿实现 Redux(⼆):抽离 store 和监控数据变化 Lesson 32 - 动⼿实现 Redux(三):纯函数(Pure Function)简介 Lesson 33 - 动⼿实现 Redux(四):共享结构的对象提⾼性能 Lesson 34 - 动⼿实现 Redux(五):不要问为什么的 reducer Lesson 35 - 动⼿实现 Redux(六):Redux 总结 Lesson 36 - 动⼿实现 React-redux(⼀):初始化⼯程 Lesson 37 - 动⼿实现 React-redux(⼆):结合 context 和 store Lesson 38 - 动⼿实现 React-redux(三):connect 和 mapStateToProps Lesson 39 - 动⼿实现 React-redux(四):mapDispatchToProps Lesson 40 - 动⼿实现 React-redux(五):Provider Lesson 41 - 动⼿实现 React-redux(六):React-redux 总结 Lesson 42 - 使⽤真正的 Redux 和 React-redux Lesson 43 - Smart 组件 vs Dumb 组件 Lesson 44 - 实战分析:评论功能(七) Lesson 45 - 实战分析:评论功能(⼋) Lesson 46 - 实战分析:评论功能(九) 特别鸣谢 特别感谢以下朋友对本书所做的审校⼯作,给本书提出了很多宝贵的改进意⻅: 邝伟科 - 腾讯 Web 前端⼯程师 杨海波 - 百度 Web ⾼级前端⼯程师 谢军令 - 天猫 Web 前端⼯程师 戴嘉华 - 前微信 Web 前端⼯程师 联系作者 邮箱:huzidaha@126.com 知乎:@胡⼦⼤哈 专栏:@前端⼤哈 有问题上知乎给我私信留⾔ License 本作品采⽤ 署名-禁⽌演绎 4.0 国际许可协议 进⾏许可 《 ⼩书》的 http://huziketang.mangojuice.top/books/react/ 版本说 3/4

4.2018/8/27 React.js⼩书 《Reac.js ⼩书》的PDF版本说明 本《React.js⼩书》的PDF版本 是由 轩辕Rowboat 使⽤node 库 puppeteer爬⾍⽣成, 仅供学习交流,严禁⽤于商业⽤途。 项⽬源代码地址:https://github.com/lxchuan12/learn- nodejs/tree/master/src/puppeteer/reactMiniBook.js http://huziketang.mangojuice.top/books/react/ 4/4

5.2018/8/27 1.React.js简介|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 1. React.js 简介 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson1 转载请注明出处,保留原⽂链接和作者信息。 React.js 是⼀个帮助你构建⻚⾯ UI 的库。如果你熟悉 MVC 概念的话,那么 React 的组件就相当于 MVC ⾥⾯的 View。如果你不熟悉也没关系,你可以简单地理解为, React.js 将帮助我们将界⾯分成了各个独⽴的⼩块,每⼀个块就是组件,这些组件之 间可以组合、嵌套,就成了我们的⻚⾯。 ⼀个组件的显⽰形态和⾏为有可能是由某些数据决定的。⽽数据是可能发⽣改变的, 这时候组件的显⽰形态就会发⽣相应的改变。⽽ React.js 也提供了⼀种⾮常⾼效的 ⽅式帮助我们做到了数据和组件显⽰形态之间的同步。 React.js 不是⼀个框架,它只是⼀个库。它只提供 UI (view)层⾯的解决⽅案。在 实际的项⽬当中,它并不能解决我们所有的问题,需要结合其它的库,例如 Redux、 React-router 等来协助提供完整的解决⽅法。 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:2. 前端组件化(⼀):从⼀个简单的例⼦讲起 http://huziketang.mangojuice.top/books/react/lesson1 1/1

6.2018/8/27 2.前端组件化(⼀):从⼀个简单的例⼦讲起|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 2. 前端组件化(⼀):从⼀个简单的例⼦讲起 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson2 转载请注明出处,保留原⽂链接和作者信息。 很多课程⼀上来就给⼤家如何配置环境、怎么写 React.js 组件。但是本课程还是希 望⼤家对问题的根源有⼀个更加深⼊的了解,其实很多的库、框架都是解决类似的问 题。只有我们对这些库、框架解决的问题有深⼊的了解和思考以后,我们才能得⼼应 ⼿地使⽤它们,并且有新的框架出来也不会太过迷茫————因为其实它们解决都是同⼀ 个问题。 这两节课我们来探讨⼀下是什么样的问题导致了我们需要前端⻚⾯进⾏组件化,前端 ⻚⾯的组件化需要解决什么样的问题。后续课程我们再来看看 React.js 是怎么解决 这些问题的。 所以这⼏节所讲的内容将和 React.js 的内容没有太⼤的关系,但是如果你能顺利了 解这⼏节的内容,那么后⾯那些对新⼿来说很复杂的概念对你来说就是⾮常⾃然的 事。 ⼀个简单的点赞功能 我们会从⼀个简单的点赞功能讲起。 假设现在我们需要实现⼀个点赞、取消点赞的功 能。 如果你对前端稍微有⼀点了解,你就顺⼿拈来: HTML: http://huziketang.mangojuice.top/books/react/lesson2 1/5

7.2018/8/27 2.前端组件化(⼀):从⼀个简单的例⼦讲起|React.js⼩书 <body> <div class='wrapper'> <button class='like-btn'> <span class='like-text'> 点赞</span> <span>👍</span> </button> </div> </body> 为了模拟现实当中的实际情况,所以这⾥特意把这个 button ⾥⾯的 HTML 结构搞得 稍微复杂⼀些。有了这个 HTML 结构,现在就给它加⼊⼀些 JavaScript 的⾏为: JavaScript: 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 结构的字符串: class LikeButton { render () { return ` <button id='like-btn'> <span class='like-text'> </span>赞 <span>👍</span> </button> ` } } http://huziketang.mangojuice.top/books/react/lesson2 2/5

8.2018/8/27 2.前端组件化(⼀):从⼀个简单的例⼦讲起|React.js⼩书 然后可以⽤这个类来构建不同的点赞功能的实例,然后把它们插到⻚⾯中。 const wrapper = document.querySelector('.wrapper') const likeButton1 = new LikeButton() wrapper.innerHTML = likeButton1.render() const likeButton2 = new LikeButton() wrapper.innerHTML += likeButton2.render() 这⾥⾮常暴⼒地使⽤了 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 类: http://huziketang.mangojuice.top/books/react/lesson2 3/5

9.2018/8/27 2.前端组件化(⼀):从⼀个简单的例⼦讲起|React.js⼩书 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> http://huziketang.mangojuice.top/books/react/lesson2 4/5

10.2018/8/27 2.前端组件化(⼀):从⼀个简单的例⼦讲起|React.js⼩书 `) this.el.addEventListener('click', this.changeLikeText.bind(this), false) return this.el } } 这⾥的代码稍微⻓了⼀些,但是还是很好理解。只不过是在给 LikeButton 类添加了 构造函数,这个构造函数会给每⼀个 LikeButton 的实例添加⼀个对象 state , state ⾥⾯保存了每个按钮⾃⼰是否点赞的状态。还改写了原来的事件绑定函数:原 来只打印 click ,现在点击的按钮的时候会调⽤ changeLikeText ⽅法,这个⽅法会 根据 this.state 的状态改变点赞按钮的⽂本。 现在这个组件的可复⽤性已经很不错了,你的同事们只要实例化⼀下然后插⼊到 DOM ⾥⾯去就好了。 下⼀节我们继续优化这个例⼦,让它更加通⽤。 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:3. 前端组件化(⼆):优化 DOM 操作 上⼀节:1. React.js 简介 http://huziketang.mangojuice.top/books/react/lesson2 5/5

11.2018/8/27 3.前端组件化(⼆):优化DOM操作|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 3. 前端组件化(⼆):优化 DOM 操作 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson3 转载请注明出处,保留原⽂链接和作者信息。 看看上⼀节我们的代码,仔细留意⼀下 changeLikeText 函数,这个函数包含了 DOM 操作,现在看起来⽐较简单,那是因为现在只有 isLiked ⼀个状态。由于数据状态 改变会导致需要我们去更新⻚⾯的内容,所以假想⼀下,如果你的组件依赖了很多状 态,那么你的组件基本全部都是 DOM 操作。 ⼀个组件的显⽰形态由多个状态决定的情况⾮常常⻅。代码中混杂着对 DOM 的操作其 实是⼀种不好的实践,⼿动管理数据和 DOM 之间的关系会导致代码可维护性变差、容 易出错。所以我们的例⼦这⾥还有优化的空间:如何尽量减少这种⼿动 DOM 操作? 状态改变 -> 构建新的 DOM 元素更新⻚⾯ 这⾥要提出的⼀种解决⽅案:⼀旦状态发⽣改变,就重新调⽤ render ⽅法,构建⼀ 个新的 DOM 元素。这样做的好处是什么呢?好处就是你可以在 render ⽅法⾥⾯使 ⽤最新的 this.state 来构造不同 HTML 结构的字符串,并且通过这个字符串构造不 同的 DOM 元素。⻚⾯就更新了!听起来有点绕,看看代码怎么写,修改原来的代码 为: class LikeButton { constructor () { this.state = { isLiked: false } } setState (state) { this.state = state this.el = this.render() } changeLikeText () { this.setState({ isLiked: !this.state.isLiked }) } render () { this.el = createDOMFromString(` http://huziketang.mangojuice.top/books/react/lesson3 1/3

12.2018/8/27 3.前端组件化(⼆):优化DOM操作|React.js⼩书 <button class='like-btn'> <span class='like-text'>${this.state.isLiked ? ' 取消 ' : ' 点赞 '}</span> <span>👍</span> </button> `) this.el.addEventListener('click', this.changeLikeText.bind(this), false) return this.el } } 其实只是改了⼏个⼩地⽅: 1. render 函数⾥⾯的 HTML 字符串会根据 this.state 不同⽽不同(这⾥是⽤了 ES6 的模版字符串,做这种事情很⽅便)。 2. 新增⼀个 setState 函数,这个函数接受⼀个对象作为参数;它会设置实例的 state ,然后重新调⽤⼀下 render ⽅法。 3. 当⽤⼾点击按钮的时候, changeLikeText 会构建新的 state 对象,这个新的 state ,传⼊ setState 函数当中。 这样的结果就是,⽤⼾每次点击, changeLikeText 都会调⽤改变组件状态然后调⽤ setState ; setState 会调⽤ render , render ⽅法会根据 state 的不同重新构建 不同的 DOM 元素。 也就是说,你只要调⽤ setState ,组件就会重新渲染。我们顺利地消除了⼿动的 DOM 操作。 重新插⼊新的 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) } ... 使⽤这个组件的时候: http://huziketang.mangojuice.top/books/react/lesson3 2/3

13.2018/8/27 3.前端组件化(⼆):优化DOM操作|React.js⼩书 const likeButton = new LikeButton() wrapper.appendChild(likeButton.render()) // 第⼀次插⼊ DOM 元素 likeButton.onStateChange = (oldEl, newEl) => { wrapper.insertBefore(newEl, oldEl) // 插⼊新的元素 wrapper.removeChild(oldEl) // 删除旧的元素 } 这⾥每次 setState 都会调⽤ onStateChange ⽅法,⽽这个⽅法是实例化以后时候被 设置的,所以你可以⾃定义 onStateChange 的⾏为。这⾥做的事是,每当 setState 中构造完新的 DOM 元素以后,就会通过 onStateChange 告知外部插⼊新的 DOM 元 素,然后删除旧的元素,⻚⾯就更新了。这⾥已经做到了进⼀步的优化了:现在不需 要再⼿动更新⻚⾯了。 ⾮⼀般的暴⼒,因为每次 setState 都重新构造、新增、删除 DOM 元素,会导致浏览 器进⾏⼤量的重排,严重影响性能。不过没有关系,这种暴⼒⾏为可以被⼀种叫 Virtual-DOM 的策略规避掉,但这不是本⽂所讨论的范围。 这个版本的点赞功能很不错,我可以继续往上⾯加功能,⽽且还不需要⼿动操作DOM。 但是有⼀个不好的地⽅,如果我要重新另外做⼀个新组件,譬如说评论组件,那么⾥ ⾯的这些 setState ⽅法要重新写⼀遍,其实这些东西都可以抽出来,变成⼀个通⽤ 的模式。下⼀节我们把这个通⽤模式抽离到⼀个类当中。 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:4. 前端组件化(三):抽象出公共组件类 上⼀节:2. 前端组件化(⼀):从⼀个简单的例⼦讲起 http://huziketang.mangojuice.top/books/react/lesson3 3/3

14.2018/8/27 4.前端组件化(三):抽象出公共组件类|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 4. 前端组件化(三):抽象出公共组件类 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson4 转载请注明出处,保留原⽂链接和作者信息。 为了让代码更灵活,可以写更多的组件,我们把这种模式抽象出来,放到⼀个 Component 类当中: class Component { setState (state) { const oldEl = this.el this.state = state this._renderDOM() if (this.onStateChange) this.onStateChange(oldEl, this.el) } _renderDOM () { this.el = createDOMFromString(this.render()) if (this.onClick) { this.el.addEventListener('click', this.onClick.bind(this), false) } return this.el } } 这个是⼀个组件⽗类 Component ,所有的组件都可以继承这个⽗类来构建。它定义的 两个⽅法,⼀个是我们已经很熟悉的 setState ;⼀个是私有⽅法 _renderDOM 。 _renderDOM ⽅法会调⽤ this.render 来构建 DOM 元素并且监听 onClick 事件。所 以,组件⼦类继承的时候只需要实现⼀个返回 HTML 字符串的 render ⽅法就可以 了。 还有⼀个额外的 mount 的⽅法,其实就是把组件的 DOM 元素插⼊⻚⾯,并且在 setState 的时候更新⻚⾯: const mount = (component, wrapper) => { wrapper.appendChild(component._renderDOM()) component.onStateChange = (oldEl, newEl) => { wrapper.insertBefore(newEl, oldEl) wrapper.removeChild(oldEl) http://huziketang.mangojuice.top/books/react/lesson4 1/4

15.2018/8/27 4.前端组件化(三):抽象出公共组件类|React.js⼩书 } } 这样的话我们重新写点赞组件就会变成: class LikeButton extends Component { constructor () { super() this.state = { isLiked: false } } onClick () { this.setState({ isLiked: !this.state.isLiked }) } render () { return ` <button class='like-btn'> <span class='like-text'>${this.state.isLiked ? ' 取消' : '点赞'}</span> <span>👍</span> </button> ` } } mount(new LikeButton(), wrapper) 这样还不够好。在实际开发当中,你可能需要给组件传⼊⼀些⾃定义的配置数据。例 如说想配置⼀下点赞按钮的背景颜⾊,如果我给它传⼊⼀个参数,告诉它怎么设置⾃ ⼰的颜⾊。那么这个按钮的定制性就更强了。所以我们可以给组件类和它的⼦类都传 ⼊⼀个参数 props ,作为组件的配置参数。修改 Component 的构造函数为: ... constructor (props = {}) { this.props = props } ... 继承的时候通过 super(props) 把 props 传给⽗类,这样就可以通过 this.props 获 取到配置参数: class LikeButton extends Component { constructor (props) { super(props) this.state = { isLiked: false } http://huziketang.mangojuice.top/books/react/lesson4 2/4

16.2018/8/27 4.前端组件化(三):抽象出公共组件类|React.js⼩书 } onClick () { this.setState({ isLiked: !this.state.isLiked }) } render () { return ` <button class='like-btn' style="background-color: ${this.props.bgColor}"> <span class='like-text'> ${this.state.isLiked ? ' 取消 ' : ' 点赞 '} </span> <span>👍</span> </button> ` } } mount(new LikeButton({ bgColor: 'red' }), wrapper) 这⾥我们稍微修改了⼀下原有的 LikeButton 的 render ⽅法,让它可以根据传⼊的 参数 this.props.bgColor 来⽣成不同的 style 属性。这样就可以⾃由配置组件的颜 ⾊了。 只要有了上⾯那个 Component 类和 mount ⽅法加起来不⾜40⾏代码就可以做到组件 化。如果我们需要写另外⼀个组件,只需要像上⾯那样,简单地继承⼀下 Component 类就好了: class RedBlueButton extends Component { constructor (props) { super(props) this.state = { color: 'red' } } onClick () { this.setState({ color: 'blue' }) } render () { return ` <div style='color: ${this.state.color};'>${this.state.color}</div> ` } } http://huziketang.mangojuice.top/books/react/lesson4 3/4

17.2018/8/27 4.前端组件化(三):抽象出公共组件类|React.js⼩书 简单好⽤,现在可以灵活地组件化⻚⾯了。 Component 完整的代码可以在这⾥找到 reactjs-in-40。 总结 我们⽤了很⻓的篇幅来讲⼀个简单的点赞的例⼦,并且在这个过程⾥⾯⼀直在优化编 写的⽅式。最后抽离出来了⼀个类,可以帮助我们更好的做组件化。在这个过程⾥⾯ 我们学到了什么? 组件化可以帮助我们解决前端结构的复⽤性问题,整个⻚⾯可以由这样的不同的组件 组合、嵌套构成。 ⼀个组件有⾃⼰的显⽰形态(上⾯的 HTML 结构和内容)⾏为,组件的显⽰形态和⾏ 为可以由数据状态(state)和配置参数(props)共同决定。数据状态和配置参数的 改变都会影响到这个组件的显⽰形态。 当数据变化的时候,组件的显⽰需要更新。所以如果组件化的模式能提供⼀种⾼效的 ⽅式⾃动化地帮助我们更新⻚⾯,那也就可以⼤⼤地降低我们代码的复杂度,带来更 好的可维护性。 好了,课程结束了。你已经学会了怎么使⽤ React.js 了,因为我们已经写了⼀个—— 当然我是在开玩笑,但是上⾯这个 Component 类其实和 React 的 Component 使⽤⽅ 式很类似。掌握了这⼏节的课程,你基本就掌握了基础的 React.js 的概念。 接下来我们开始正式进⼊主题,开始正式介绍 React.js。你会发现,有了前⾯的铺 垫,下⾯讲的内容理解起来会简单很多了。 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:5. React.js 基本环境安装 上⼀节:3. 前端组件化(⼆):优化 DOM 操作 http://huziketang.mangojuice.top/books/react/lesson4 4/4

18.2018/8/27 5.React.js基本环境安装|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 5. React.js 基本环境安装 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson5 转载请注明出处,保留原⽂链接和作者信息。 安装 React.js React.js 单独使⽤基本上是不可能的事情。不要指望着类似于 jQuery 下载放到 <head /> 标签就开始使⽤。使⽤ React.js 不管在开发阶段⽣产阶段都需要⼀堆⼯具 和库辅助,编译阶段你需要借助 Babel;需要 Redux 等第三⽅的状态管理⼯具来组织 代码;如果你要写单⻚⾯应⽤那么你需要 React-router。这就是所谓的“React.js全 家桶”。 本课程不会教⼤家如何配置这些东西,因为这不是课程的重点,⽹上有很多的资料, ⼤家可以去参考那些资料。我们这⾥会直接使⽤ React.js 官⽹所推荐使⽤的⼯具 create-react-app ⼯具。它可以帮助我们⼀键⽣成所需要的⼯程⽬录,并帮我们做好 各种配置和依赖,也帮我们隐藏了这些配置的细节。也就是所谓的“开箱即⽤”。 ⼯具地址:https://github.com/facebookincubator/create-react-app 在安装之前要确认你的机器上安装了 node.js 环境包括 npm。如果没有安装的同学可 以到 node.js 的官⽹下载⾃⼰电脑的对应的安装包来安装好环境。 http://huziketang.mangojuice.top/books/react/lesson5 1/4

19.2018/8/27 5.React.js基本环境安装|React.js⼩书 安装好环境以后,只需要按照官⽹的指引安装 create-react-app 即可。 npm install -g create-react-app 这条命令会往我们的机器上安装⼀条叫 create-react-app 的命令,安装好以后就可以 直接使⽤它来构建⼀个 react 的前端⼯程: create-react-app hello-react 这条命令会帮我们构建⼀个叫 hello-react 的⼯程,并且会⾃动地帮助我们安装所需 要的依赖,现在只需要安静地等待它安装完。 额外的⼩贴⼠: 如果有些同学安装过程⽐较慢,那是很有可能是因为 npm 下载的时候是从国外的源下载的缘 故。所以可以把 npm 的源改成国内的 taobao 的源,这样会加速下载过程。在执⾏上⾯的命令 之前可以先修改⼀下 npm 的源: npm config set registry https://registry.npm.taobao.org 下载完以后我们就可以启动⼯程了,进⼊⼯程⽬录然后通过 npm 启动⼯程: cd hello-react npm start 终端提⽰成功: http://huziketang.mangojuice.top/books/react/lesson5 2/4

20.2018/8/27 5.React.js基本环境安装|React.js⼩书 并且会⾃动打开浏览器,就可以看到 React 的⼯程顺利运⾏的效果: 这时候我们把 src/App.js ⽂件中的 <h2> 标签的内容修改为 Hello React , <h2>Hello React</h2> 保存⼀下,然后⼾就会发现浏览器⾃动刷新,并且我们的修改也⽣效了: http://huziketang.mangojuice.top/books/react/lesson5 3/4

21.2018/8/27 5.React.js基本环境安装|React.js⼩书 到这⾥我们的环境已经安装好了,并且顺利地运⾏了我们第⼀个例⼦。接下来我们会 探讨 React.js 的组件的基本写法。 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:6. 使⽤ JSX 描述 UI 信息 上⼀节:4. 前端组件化(三):抽象出公共组件类 http://huziketang.mangojuice.top/books/react/lesson5 4/4

22.2018/8/27 6.使⽤JSX描述UI信息|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 6. 使⽤ JSX 描述 UI 信息 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson6 转载请注明出处,保留原⽂链接和作者信息。 这⼀节我们通过⼀个简单的例⼦讲解 React.js 描述⻚⾯ UI 的⽅式。把 src/index.js 中的代码改成: import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( <div> <h1>React ⼩书 </h1> </div> ) } } ReactDOM.render( <Header />, document.getElementById('root') ) 我们在⽂件头部从 react 的包当中引⼊了 React 和 React.js 的组件⽗类 Component 。记住,只要你要写 React.js 组件,那么就必须要引⼊这两个东西。 ReactDOM 可以帮助我们把 React 组件渲染到⻚⾯上去,没有其它的作⽤了。你可以 发现它是从 react-dom 中引⼊的,⽽不是从 react 引⼊。有些朋友可能会疑惑,为 什么不把这些东西都包含在 react 包当中呢?我们稍后会回答这个问题。 接下来的代码你看起来会⽐较熟悉,但⼜会有点陌⽣。你看其实它跟我们前⼏节⾥⾯ 讲的内容其实很类似,⼀个组件继承 Component 类,有⼀个 render ⽅法,并且把 这个组件的 HTML 结构返回;这⾥ return 的东西就⽐较奇怪了,它并不是⼀个字符 串,看起来像是纯 HTML 代码写在 JavaScript 代码⾥⾯。你也许会说,这不就有语 http://huziketang.mangojuice.top/books/react/lesson6 1/5

23.2018/8/27 6.使⽤JSX描述UI信息|React.js⼩书 法错误了么?这完全不是合法的 JavaScript 代码。这种看起来“在 JavaScript 写的 标签的”语法叫 JSX。 JSX 原理 为了让⼤家深刻理解 JSX 的含义。有必要简单介绍了⼀下 JSX 稍微底层的运作原 理,这样⼤家可以更加深刻理解 JSX 到底是什么东西,为什么要有这种语法,它是经 过怎么样的转化变成⻚⾯的元素的。 思考⼀个问题:如何⽤ JavaScript 对象来表现⼀个 DOM 元素的结构,举个例⼦: <div class='box' id='content'> <div class='title'>Hello</div> <button>Click</button> </div> 每个 DOM 元素的结构都可以⽤ JavaScript 的对象来表⽰。你会发现⼀个 DOM 元素 包含的信息其实只有三个:标签名,属性,⼦元素。 所以其实上⾯这个 HTML 所有的信息我们都可以⽤合法的 JavaScript 对象来表⽰: { tag: 'div', attrs: { className: 'box', id: 'content'}, children: [ { tag: 'div', arrts: { className: 'title' }, children: ['Hello'] }, { tag: 'button', attrs: null, children: ['Click'] } ] } 你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是⼀样的,我们可以 ⽤ JavaScript 对象来描述所有能⽤ HTML 表⽰的 UI 信息。但是⽤ JavaScript 写 起来太⻓了,结构看起来⼜不清晰,⽤ HTML 的⽅式写起来就⽅便很多了。 于是 React.js 就把 JavaScript 的语法扩展了⼀下,让 JavaScript 语⾔能够⽀持 这种直接在 JavaScript 代码⾥⾯编写类似 HTML 标签结构的语法,这样写起来就⽅ 便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。 http://huziketang.mangojuice.top/books/react/lesson6 2/5

24.2018/8/27 6.使⽤JSX描述UI信息|React.js⼩书 上⾯的代码: import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( <div> <h1 className='title'>React ⼩书</h1> </div> ) } } ReactDOM.render( <Header />, document.getElementById('root') ) 经过编译以后会变成: import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( React.createElement( "div", null, React.createElement( "h1", { className: 'title' }, "React "⼩书 ) ) ) } } ReactDOM.render( React.createElement(Header, null), document.getElementById('root') ); http://huziketang.mangojuice.top/books/react/lesson6 3/5

25.2018/8/27 6.使⽤JSX描述UI信息|React.js⼩书 会构建⼀个 JavaScript 对象来描述你 HTML 结构的信息,包括 React.createElement 标签名、属性、还有⼦元素等。这样的代码就是合法的 JavaScript 代码了。所以使 ⽤ React 和 JSX 的时候⼀定要经过编译的过程。 这⾥再重复⼀遍:所谓的 JSX 其实就是 JavaScript 对象。每当在 JavaScript 代码 中看到这种 JSX 结构的时候,脑⼦⾥⾯就可以⾃动做转化,这样对你理解 React.js 的组件写法很有好处。 有了这个表⽰ HTML 结构和信息的对象以后,就可以拿去构造真正的 DOM 元素,然后 把这个 DOM 元素塞到⻚⾯上。这也是我们最后⼀段代码中 ReactDOM.render 所⼲的 事情: ReactDOM.render( <Header />, document.getElementById('root') ) 功能就是把组件渲染并且构造 DOM 树,然后插⼊到⻚⾯上某个特定 ReactDOM.render 的元素上(在这⾥是 id 为 root 的 div 元素)。 所以可以总结⼀下从 JSX 到⻚⾯到底经过了什么样的过程: 有些同学可能会问,为什么不直接从 JSX 直接渲染构造 DOM 结构,⽽是要经过中间 这么⼀层呢? 第⼀个原因是,当我们拿到⼀个表⽰ UI 的结构和信息的对象以后,不⼀定会把元素 渲染到浏览器的普通⻚⾯上,我们有可能把这个结构渲染到 canvas 上,或者是⼿机 App 上。所以这也是为什么会要把 react-dom 单独抽离出来的原因,可以想象有⼀个 叫 react-canvas 可以帮我们把 UI 渲染到 canvas 上,或者是有⼀个叫 react-app 可以帮我们把它转换成原⽣的 App(实际上这玩意叫 ReactNative )。 第⼆个原因是,有了这样⼀个对象。当数据变化,需要更新组件的时候,就可以⽤⽐ 较快的算法操作这个 JavaScript 对象,⽽不⽤直接操作⻚⾯上的 DOM,这样可以尽 http://huziketang.mangojuice.top/books/react/lesson6 4/5

26.2018/8/27 6.使⽤JSX描述UI信息|React.js⼩书 量少的减少浏览器重排,极⼤地优化性能。这个在以后的章节中我们会提到。 总结 要记住⼏个点: 1. JSX 是 JavaScript 语⾔的⼀种语法扩展,⻓得像 HTML,但并不是 HTML。 2. React.js 可以⽤ JSX 来描述你的组件⻓什么样的。 3. JSX 在编译的时候会变成相应的 JavaScript 对象描述。 4. react-dom 负责把这个⽤来描述 UI 信息的 JavaScript 对象变成 DOM 元素, 并且渲染到⻚⾯上。 课后练习题 ⽤ React.js 在⻚⾯上渲染标题 因为第三⽅评论⼯具有问题,对本章节有任何疑问的朋友可以移步到 React.js ⼩书的论坛 发 帖,我会回答⼤家的疑问。 下⼀节:7. 组件的 render ⽅法 上⼀节:5. React.js 基本环境安装 http://huziketang.mangojuice.top/books/react/lesson6 5/5

27.2018/8/27 7.组件的render⽅法|React.js⼩书 ⼩书 React.js <-- 返回⾸⻚ 7. 组件的 render ⽅法 作者:胡⼦⼤哈 原⽂链接: http://huziketang.com/books/react/lesson7 转载请注明出处,保留原⽂链接和作者信息。 中⼀切皆组件,⽤ React.js 写的其实就是 React.js 组件。我们在编写 React.js 组件的时候,⼀般都需要继承 React.js 的 Component (还有别的编写组件 React.js 的⽅式我们后续会提到)。⼀个组件类必须要实现⼀个 render ⽅法,这个 render ⽅法必须要返回⼀个 JSX 元素。但这⾥要注意的是,必须要⽤⼀个外层的 JSX 元素 把所有内容包裹起来。返回并列多个 JSX 元素是不合法的,下⾯是错误的做法: ... render () { return ( <div> 第⼀个</div> <div> 第⼆个</div> ) } ... 必须要⽤⼀个外层元素把内容进⾏包裹: ... render () { return ( <div> <div> 第⼀个</div> <div> 第⼆个</div> </div> ) } ... 表达式插⼊ 在 JSX 当中你可以插⼊ JavaScript 的表达式,表达式返回的结果会相应地渲染到⻚ ⾯上。表达式⽤ {} 包裹。例如: http://huziketang.mangojuice.top/books/react/lesson7 1/5

28.2018/8/27 7.组件的render⽅法|React.js⼩书 ... render () { const word = 'is good' return ( <div> <h1>React ⼩书 {word}</h1> </div> ) } ... ⻚⾯上就显⽰“React ⼩书 is good”。你也可以把它改成 {1 + 2} ,它就会显⽰ “React ⼩书 3”。你也可以把它写成⼀个函数表达式返回: ... render () { return ( <div> <h1>React ⼩书 {(function () { return 'is good'})()}</h1> </div> ) } ... 简⽽⾔之, {} 内可以放任何 JavaScript 的代码,包括变量、表达式计算、函数执 ⾏等等。 render 会把这些代码返回的内容如实地渲染到⻚⾯上,⾮常的灵活。 表达式插⼊不仅仅可以⽤在标签内部,也可以⽤在标签的属性上,例如: ... render () { const className = 'header' return ( <div className={className}> <h1>React ⼩书 </h1> </div> ) } ... 这样就可以为 div 标签添加⼀个叫 header 的类名。 注意,直接使⽤ class 在 React.js 的元素上添加类名如 <div class=“xxx”> 这种 ⽅式是不合法的。因为 class 是 JavaScript 的关键字,所以 React.js 中定义了 ⼀种新的⽅式: className 来帮助我们给元素添加类名。 http://huziketang.mangojuice.top/books/react/lesson7 2/5

29.2018/8/27 7.组件的render⽅法|React.js⼩书 还有⼀个特例就是 for 属性,例如 <label for='male'>Male</label> ,因为 for 也 是 JavaScript 的关键字,所以在 JSX ⽤ htmlFor 替代,即 <label htmlFor='male'>Male</label> 。⽽其他的 HTML 属性例如 style 、 data-* 等就可以像 普通的 HTML 属性那样直接添加上去。 条件返回 {}上⾯说了,JSX 可以放置任何表达式内容。所以也可以放 JSX,实际上,我们可 以在 render 函数内部根据不同条件返回不同的 JSX。例如: ... render () { const isGoodWord = true return ( <div> <h1> React ⼩书 {isGoodWord ? <strong> is good</strong> : <span> is not good</span> } </h1> </div> ) } ... 上⾯的代码中定义了⼀个 isGoodWord 变量为 true ,下⾯有个⽤ {} 包含的表达 式,根据 isGoodWord 的不同返回不同的 JSX 内容。现在⻚⾯上是显⽰ React ⼩书 is good 。如果你把 isGoodWord 改成 false 然后再看⻚⾯上就会显⽰ React ⼩书 is not good 。 如果你在表达式插⼊⾥⾯返回 null ,那么 React.js 会什么都不显⽰,相当于忽略 了该表达式插⼊。结合条件返回的话,我们就做到显⽰或者隐藏某些元素: ... render () { const isGoodWord = true return ( <div> <h1> React ⼩书 {isGoodWord ? <strong> is good</strong> : null } </h1> http://huziketang.mangojuice.top/books/react/lesson7 3/5