《深入浅出React和Redux》(1) – React基础

  • A+
所属分类:Web前端
摘要

React技术依赖的技术栈比较多,比如,转译JavaScript代码需要使用Babel,模块打包工具要使用Webpack,定制build过程需要grunt或者gulp。create-react-app命令可以免去配置这些技术栈的麻烦,自动生成一个基本的react-app模版项目,让开发者可以基于这个模版快速开始React应用的开发。
首先要安装create-react-app命令:


create-react-app

React技术依赖的技术栈比较多,比如,转译JavaScript代码需要使用Babel,模块打包工具要使用Webpack,定制build过程需要grunt或者gulp。create-react-app命令可以免去配置这些技术栈的麻烦,自动生成一个基本的react-app模版项目,让开发者可以基于这个模版快速开始React应用的开发。
首先要安装create-react-app命令:

npm install --global create-react-app 

然后就可以创建了:

create-react-app <app-name> 

组件

React的首要思想是通过组件(Component)来开发应用。所谓组件,简单说,指的是能完成某个特定功能的独立的、可重用的代码。这与传统的web开发方式差异很大,以前用HTML来代表内容,用CSS代表样式,用JavaScript来定义交互行为,这三种语言分在三种不同的文件里面,实际上是把不同技术分开管理了,而不是逻辑上的“分而治之”。组件则是按照逻辑(功能)来组织的。

Component作为所有组件的基类,提供了很多组件共有的功能

JSX

JSX, 是JavaScript的语法扩展(eXtension),允许在JavaScript中可以编写像HTML一样的代码。在JSX中使用的“元素”不局限于HTML中的元素,可以是任何一个React组件,React判断一个元素是HTML元素还是React组件的原则就是看第一个字母是否大写。

React的理念

React的理念可以归结为一个简单的公式:

UI = render(data) 

即render函数,与它接收的data决定了UI渲染的结果。render是一个纯函数,它的输出完全依赖输入。
对于开发者来说,重要的是区分开哪些属于data,哪些属于render,想要更新用户界面,要做的就是更新data,用户界面自然会做出响应。所以React实践的也是“响应式编程”(ReactiveProgramming)的思想,这也就是React为什么叫做React的原因。

但在数据发生变化时,React并不会重新渲染整个页面,它利用Virtual DOM实现了只渲染差异DOM的特性,渲染效率很高。

Virtual DOM

要了解Virtual DOM,就要先了解DOM,DOM是结构化文本的抽象表达形式,这里所谓的结构化文本即HTML文本,HTML中的每个元素都对应DOM中某个节点,HTML元素的层级关系对应DOM树。

浏览器在渲染HTML的时候,会先将HTML文本解析以构建DOM树,然后再根据DOM树渲染UI,当要改变界面内容的时候,可以通过对DOM进行操作来实现。

Web前端开发关于性能优化有一个原则:尽量减少DOM操作。因为DOM操作会引起浏览器对网页的重新绘制,这是一个相对耗时的过程。

然后Virtual DOM就是对DOM的更高一层的抽象,当React组件发生变化时,React新旧Virtual DOM之间的差异,再根据这些差异来操作真正的DOM。

React利用函数式编程的思想以及数据驱动的模式进行Web页面的开发,数据的变化触发的只是Virtual DOM的变化,剩下的部分由React去完成,开发者并不需要关心。

在开发React应用的时候,开发者使用的是声明式的语法,专注于描述操作的结果,而不用过多考虑操作的细节。

易于维护组件的设计要素

设计组件的时候,应该尽量保持一个组件只做一件事。

如果发现一个组件功能太多代码量太大的时候,就可以考虑将其拆分了;但拆分组件时要注意,组件的粒度过粗、过细都不好,这就同“一个组件只做一件事”一样要看具体的场景。

拆分组件时,最重要也最困难的是要确定组件的边界,每个组件都应该是可以独立存在的,如果两个组件逻辑太紧密,无法清晰定义各自的责任,那也许这两个组件本身就不该被拆开,作为同一个组件也许更合理。

通用的软件设计原则也适用于组件的设计:组件应该遵循高内聚、低耦合的原则。

prop和state

React组件的数据分为两种,prop和state,无论prop或者state的改变,都可能引发组件的重新渲染。prop是组件的对外接口,state用于存储组件的内部状态。

prop

一个React组件通过定义自己能够接受的prop就定义了自己的对外公共接口。props的类型可以是任何一种JavaScript语言支持的数据类型,包括函数。
当prop的类型不是字符串类型时,在JSX中必须用花括号{}把prop值包住。
React组件不能修改传入的prop的值。

propTypes用于定于组件的接口规范:
首先倒入PropTypes

import PropTypes from 'prop-types'; 

在[classname].propTypes中添加对props的限制,比如下面的代码限制caption字段为string类型,而且isRequired必须传值。

Counter.propTypes = {   caption: PropTypes.string.isRequired } 

这些添加的限制,会被用于运行时和静态代码检查,据此来判断组件是否被正确地使用。
但这种限制并不是强制的,即使被违反,组件仍能正常工作,但会在浏览器的console里抛出警告信息。

propTypes可以帮助在开发阶段尽早发现代码中的问题,但是最好不要带到产品环境中,babel-react-optimize可以在发布产品代码时自动移除定义的propTypes。

defaultProps可以用来为props中的字段定义缺省值:

Counter.defaultProps = {   initValue: 3 }; 

state

state用来记录组件自身数据的变化。
state的初始化要在组件的构造函数中进行,通过为this.state赋值就完成了其初始化:

this.state = {   count: props.initValue }; 

组件的state必须是一个JS对象,即使组件需要的只是一个数字类型的字段,也必须把它作state的一个字段。

通过this.state可以读取其中的值,比如this.state.count.

修改state的值必须通过this.setState()方法进行:

this.setState({ count: this.state.count + 1 }); 

直接修改并不会生效,这是因为setState除了会修改state的值外,还会触发页面的重新渲染。

组件的生命周期

在一个React组件的生命周期中,会经历三个过程:

  • 装载过程(Mount),也就是把组件第一次在DOM树中渲染的过程;
  • 更新过程(Update),当组件被重新渲染的过程;
  • 卸载过程(Unmount),组件从DOM中删除的过程。

装载过程

组件第一次被渲染时,主要会调用如下函数:

  • constructor
  • render
  • componentDidMount
constructor

并不是每个组件都需要定义自己的构造函数,在下面的情况下,组件才需要定义构造函数:

  • 初始化state,如果组件包含state,就需要在构造函数初始化;
  • 绑定成员函数的this环境
render

一个React组件可以忽略其他所有函数都不实现,但是一定要实现render函数,因为父类React.Component类对除render之外的生命周期函数都有默认实现。
render函数并不做实际的渲染动作,它只是返回一个JSX描述的结构,最终由React来进行渲染。
render函数应该是一个纯函数,完全根据this.state和this.props来决定返回的结果,而且不要产生任何副作用。

componentDidMount

React在根据render返回的结果来渲染页面的时候,componentDidMount会被触发,但它并不在render执行后立即触发,要等React库根据所有组件的render都执行完。

更新过程

更新过程主要会调用如下函数:

  • shouldComponentUpdate
  • render
  • componentDidUpdate
shouldComponentUpdate

shouldComponentUpdate也是一个非常重要的函数,它的返回值是bool,决定是否应该重新渲染组件。
需要优化性能时,可以定制shouldComponentUpdate。父类React.Component中的默认实现方式是始终返回true。定制时可以对比state与props,只有变化时才更新。

卸载过程

卸载过程只涉及一个函数componentWillUnmount,当React组件要从DOM树上删除掉之前,对应的componentWillUnmount函数会被调用,所以这个函数适合做一些清理性的工作。
比如在componentDidMount中用非React的方法创造了一些DOM元素,如果撒手不管可能会造成内存泄露,那就需要在componentWillUnmount中把这些创造的DOM元素清理掉。

组件向外传递数据

通过prop可以从父组件传递数据给子组件,很多时候也需要将数据从子组件返回给父组件,这时仍然可以利用prop来实现。
prop支持任何类型的JavaScript对象,包括函数,函数可以像其他对象一样作为prop的值从父组件传递给子组件,又可以被子组件作为函数调用,于是可以借助函数将数据从子组件返回给父组件。

但这种传递方式在涉及到多个、多层组件之间的数据传递时就比较复杂了,多层组件的数据需要层层传递,平级组件之间的数据又需要依赖父组件的中转,可能导致数据冗余、数据流混乱。接下来看看redux是如何解决这类问题的。

参考书籍

《深入浅出React和Redux》 程墨