《React进阶之路》笔记

一、扎实基础,无招胜有招

前端领域各种新技术、新思想不断涌现,AngularJS、React、Vue.js、Node.js、ES6、ES7、CoffeeScript、TypeScript,令人眼花缭乱。对于许多开发者,估计还没学明白一样技术,就发现其已被另一些新的技术取代而“过时”了。但是,如果退一步来看,前端的基本功仍然是html、css、JavaScript,还有算法、数据结构、编译原理。这一点,有点像《笑傲江湖》里,令狐冲一旦领悟了独孤九剑,永远能够无招胜有招。
除了具备扎实的基本功之外,一个优秀的前端工程师必须要有自己擅长的领域,并且钻研得足够深入,只有花时间学习成体系的知识才能从中总结出规律并形成方法论,从而最大化学习的价值。同时要有广泛的视野,不能局限于前端本身,因为有很多东西只有站在前端之外才能看得更清晰、更透彻。例如,React集成了许多后端的优秀理念,包括采用声明范式轻松描述应用、通过抽象DOM来达到高效的编程。围绕React还出现了许多工具和框架,形成了React生态。React逐渐从最早的UI引擎变成了前后通吃的Web App解决方案,衍生出来的React Native又实现了用Web App的方式去写Native App。

二、React简介

前端UI的本质问题是如何将来源于服务器端的动态数据和用户的交互行为高效地反映到复杂的用户界面上。React另辟蹊径,通过引入虚拟DOM、状态、单向数据流等设计理念,形成以组件为核心,用组件搭建UI的开发模式,理顺了UI开发过程,完美地将数据、组件状态和UI映射到一起,极大地提高了开发大型Web应用的效率。

三、React的特点可以总结为以下4点

1、声明式的视图层。使用React再也不用担心数据、状态和视图层交错纵横在一起了。React的视图层是声明式的,基于视图状态声明视图形式。
2、简单的更新流程。你只需要定义UI状态,React便会负责把它渲染成最终的UI。当状态数据发生变化时,React 也会根据最新的状态渲染出最新的UI。
3、灵活的渲染实现。React并不是把视图层直接渲染成最终的终端界面,而是先把他们渲染成虚拟DOM。虚拟DOM只是普通的JavaScript对象,你可以结合其他依赖库把这个对象渲染成不同终端上的UI。例如,使用react-dom在浏览器上渲染,使用Node在服务器端渲染,使用React Native 在手机上渲染。
4、高效的DOM操作。React可以尽量减少虚拟DOM到真实DOM的渲染次数,以及每次渲染需要改变的真实DOM节点数。

四、ES6模块化标准

ES6实现了自己的模块化标准,ES6模块化功能主要由两个关键字构成:export和import。export用于规定模块对外暴露的接口,import用于引入其他模块提供的接口。

五、开发编译过程需要Node.js

Node.js是一个JavaScript运行时,它让JavaScipt在服务器端运行成为现实。React应用的执行并不依赖于Node.js环境,但React应用开发编译过程中用到的很多依赖(如NPM、Webpack等)都是需要Node.js环境的。所以,在开发React应用前,需要先安装Node.js。

六、Babel

React应用中会大量使用ES6语法,但是目前的浏览器环境并不完全支持ES6语法。Babel是一个JavaScript编译器,它可以将ES6及其以后的语法编译成ES5的语法,从而让我们可以在开发过程中尽情使用新的JavaScript语法,而不需要担心代码无法在浏览器端运行的问题。Babel一般会和Webpack一起使用,在Webpack编译打包的阶段,利用Babel插件将ES6及其以后的语法编译成ES5语法。

七、JSX简介

JSX是一种用于描述UI的JavaScript扩展语法,React使用这种语法描述组件的UI。长期以来,UI和数据分离一直是前端领域的一个重要关注点。为了解决这个问题,前端领域发明了模板,将UI的定义放入模板文件,将数据的逻辑维护在JS代码中,然后通过模版引擎,根据数据和模板文件渲染出最终的html文件或代码片段。React致力于通过组件的概念将页面进行拆分并实现组件复用。React认为,一个组件应该是具备UI描述和UI数据的完整体,不应该将它们分开处理,于是发明了JSX,作为UI描述和UI数据之间的桥梁。这样,在组件内部可以使用类似html的标签描述组件的UI,让UI结构直观清晰,同时因为JSX本质上仍然是JavaScript,所以可以使用更多的JS语法,构建更加复杂的UI结构。

八、JSX标签类型

在JSX语法中,使用的标签类型有两种:DOM类型的标签(div、span等)和React组件类型的标签。当使用DOM类型的标签时,标签的首字母必须小写;当使用React组件类型的标签时,组件名称的首字母必须大写。React正是通过首字母的大小写判断渲染的是一个DOM类型的标签还是一个React组件类型的标签。

九、JavaScript表达式

JSX可以使用Javascript表达式,因为JSX本质上仍然是JavaScript。在JSX中使用JavaScript表达式需要将表达式用大括号{}抱起来。表达式在JSX中的使用场景主要有两个:通过表达式给标签属性赋值和通过表达式定义子组件。
注意:JSX中只能使用JavaScript表达式,而不能使用多行JavaScript语句。不过,JSX中可以使用三目运算符或逻辑与(&&)运算符代替if语句的作用。

十、JSX标签属性

当JSX标签是DOM类型的标签时,对应DOM标签支持的属性JSX也支持,例如:id、class、style、onclick等。但是,部分属性的名称会有所变化,主要的变化有:class要写成className,事件属性名称采用驼峰格式,例如onclick要写成onClick。原因是,class是JavaScript的关键字,所以改成className;React对DOM标签支持的事件重新做了封装,封装时采用了更常用的驼峰命名法命名事件。当JSX标签是React组件类型时,可以任意定义标签的属性名。

十一、JSX注释

JSX中的注释需要用大括号{}将/**/包裹起来。例如:

   const element = (
        
{/*这里是一个注释*/} React
)

十二、JSX不是必需的

JSX语法对使用React来说并不是必需的,实际上,JSX语法只是

 React.createElement(component,props,...children)

的语法糖,所有的JSX语法最终都会被转换成对这个方法的调用。例如:

//JSX语法
const element = 
Hello,React
//转换后 const element = React.createElement('div',{className:'foo'},'Hello,React')

虽然JSX只是一个语法糖,但使用它创建界面元素更加清晰简洁,在项目中使用应该首选JSX语法。

十三、组件定义

组件是React的核心概念,是React应用程序的基石。组件将应用的UI拆分成独立、可复用的模块,React应用程序正是由一个一个组件搭建而成的。
定义一个组件有两种方式,使用ES6 class(类组件)和使用函数(函数组件)。
使用class 定义组件需要满足两个条件:
(1)class继承自React.Component
(2) class 内部必须定义render方法,render方法返回代表该组件UI的React元素。
在定义组件之后,使用ES6 export将组件做为默认模块导出,从而可以在其他JS文件中导入组件使用。
使用ReactDOM.render() 需要先导入react-dom库,这个库会完成组件所代表的虚拟DOM节点到浏览器的DOM节点的转换。

十四、组件的props

组件的props用于把父组件中的数据或方法传递给子组件,供子组件使用。props是一个简单结构的对象,它包含的属性正是由组件作为JSX标签使用时的属性组成的。

十五、组件的state

组件的state是组件内部的状态,state的变化最终将反映到组件UI的变化上。我们在组件的构造方法constructor中通过this.state定义组件的初始状态,并通过调用this.setState方法改变组件状态(也是改变组件状态的唯一方式),进而组件UI也会随之重新渲染。

十六、组件的props && state

React组件正是由props和state两种类型的数据驱动渲染出组件UI。props是组件对外的接口,组件通过props接收外部传入的数据(包括方法);state是组件对内的接口,组件内部状态的变化通过state来反映。另外,props是只读的,你不能在组件内部修改props;state是可变的,组件状态的变化通过修改state来实现。

十七、有状态组件和无状态组件

  • 是不是每个组件内部都需要定义state呢?当然不是。state用来反映组件内部状态的变化,如果一个组件的内部状态是不变的,当然就用不到state,这样的组件称之为无状态组件。反之,一个组件的内部状态会发生变化,就需要用state 来保存变化,这样的组件称之为有状态组件。
  • 定义无状态组件除了使用ES 6 class的方式外,还可以使用函数定义,也就是函数组件。函数组件的写法比类组件的写法要简洁很多,在使用无状态组件时,应该尽量将其定义成函数组件。
  • 在开发React应用时,一定要先认真思考哪些组件应该设计成状态组件,哪些组件应该设计成无状态组件。并且,应该尽可能多的使用无状态组件,无状态组件不用关心状态的变化,只聚焦于UI的展示,因而更容易被复用。
  • React应用组件设计的一般思路是,通过定义少数的有状态组件管理整个应用的状态变化,并且将状态通过props传递给其余的无状态组件,由无状态组件完成页面绝大部分UI的渲染工作。总之,有状态组件主要关注处理状态变化的业务逻辑,无状态组件主要关注组件UI的渲染。

十八、组件的生命周期

组件从被创建到被销毁的过程称为组件的生命周期。React为组件在不同的生命周期阶段提供不同的生命周期方法,让开发者可以在组件的生命周期过程中更好地控制组件的行为。通常,组件的生命周期可以被分为三个阶段:挂载阶段、更新阶段、卸载阶段。

十九、组件的生命周期-挂载阶段

这个阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件的第一次渲染。依次调用的生命周期方法有:

  • constructor
  • componentWillMount
  • render
  • componentDidMount

二十、组件的生命周期-挂载阶段-constructor

这是ES 6 class的构造方法,组件被创建时,会首先调用组件的构造方法。这个构造方法接收一个props参数,props是从父组件中传入的属性对象,如果父组件中没有传入属性而组件自身定义了默认属性,那么这个props指向的就是组件的默认属性。你必须在这个方法中首先调用super(props)才能保证props被传入组件中。constructor通常用于初始化组件的state以及绑定事件处理方法等工作。

二十一、组件的生命周期-挂载阶段-componentWillMount

这个方法在组件被挂载到DOM前调用,且只会被调用一次。这个方法在实际项目中很少会用到,因为可以在该方法中执行的工作都可以提前到constructor中。在这个方法中调用this.setState不会引起组件的重新渲染。

二十二、组件的生命周期-挂载阶段-render

这是定义组件时唯一必要的方法(组件的其他生命周期方法都可以省略)。在这个方法中,根据组件的props和state返回一个React元素,用于描述组件的UI,通常React元素使用JSX语法定义。需要注意的是,render并不负责组件的实际渲染工作,它只是返回一个UI的描述,真正的渲染出页面DOM的工作由React自身负责。render是一个纯函数,在这个方法中不能执行任何有副作用的操作,所以不能在render中调用this.setState,这会改变组件的状态。

二十三、组件的生命周期-挂载阶段-componentDidMount

在组件被挂载到DOM后调用,且只会被调用一次。这时候已经可以获取到DOM结构,因此依赖DOM节点的操作可以放到这个方法中。这个方法通常还会用于向服务器请求数据。在这个方法调用this.setState会引起组件的重新渲染。

二十四、组件的生命周期-更新阶段

组件的更新阶段,依次调用的生命周期方法有:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

二十五、组件的生命周期-卸载阶段

组件从DOM中被卸载的过程,这个过程中只有一个生命周期方法:
componentWillUnmount
这个方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清除组件中使用的定时器,清除componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。
只有类组件才具有生命周期方法,函数组件是没有生命周期方法的,因此永远不要在函数组件中使用生命周期方法。

二十六、事件

  • 在React中,事件的命名采用驼峰命名方式,而不是DOM元素中的小写字母命名方式。例如,onclick要写成onClick。
  • 处理事件的响应函数要以对象的形势赋值给事件属性,而不是DOM中的字符串形势。在DOM中绑定一个点击事件这样写

而在React元素中绑定一个点击事件变成这种形式:

  • React中的事件是合成事件,并不是原生的DOM事件。React根据W3C规范定义了一套兼容各个浏览器的事件对象。在DOM事件中,可以通过处理函数返回false来阻止事件的默认行为,但在React事件中,必须显示地调用事件对象的preventDefault方法来阻止事件的的默认行为。如果在某些场景下必须使用DOM提供的原生事件,可以通过React事件对象的nativeEvent属性获取。

二十七、父子组件通信

父组件向子组件通信是通过父组件向子组件的props传递数据完成的。当子组件需要向父组件通信时,又该怎么做呢?答案依然是props。父组件可以通过子组件的props传递给子组件一个回调函数,子组件在需要改变父组件数据时,调用这个回调函数即可。
总结:父子组件通信的桥梁就是通过props传递的数据和回调方法。

二十八、兄弟组件通信

当两个组件不是父子关系但有相同的父组件时,称为兄弟组件。注意,这里的兄弟组件在整个组件树上并不一定处于同一层级。
兄弟组件不能直接相互传送数据,需要通过状态提升的方式实现兄弟组件的通信,即把组件之间需要共享的状态保存到距离它们最近的共同父组件内,任意一个兄弟组件都可以通过父组件传递的回调函数来修改共同状态,父组件中共享的变化也通过props向下传递给所有兄弟组件,从而完成兄弟组件之间的通信。

二十九、特殊的ref

ref不仅可以用来获取表单元素,还可以用来获取其他任意DOM元素,甚至可以用来获取React组件实例。在一些场景下,ref的使用可以带来便利,例如控制元素的焦点、文本的选择或者和第三方操作DOM的库集成。但绝大多数场景下,应该避免使用ref,因为它破坏了React中以props为数据传递介质的典型数据流。

三十、虚拟DOM

React之所以执行效率高,一个很重要的原因是它的虚拟DOM机制。
虚拟DOM是和真实DOM相对应的,真实DOM也就是平时我们所说的DOM,它是对结构化文本的抽象表达。在Web环境中,其实就是对html文本的一种抽象描述,每一个html元素对应一个DOM节点,html元素的层级关系也会体现在DOM节点的层级上,所有的这些DOM节点构成一棵DOM树。
在传统的前端开发中,通过调用浏览器提供的一组API直接对DOM执行增删改查的操作。例如,通过getElementById查询一个DOM节点,通过insertBefore在某个节点前插入一个新的节点等。这些操作看似只执行了一条JavaScript语法,但它们的执行效率要比执行一条普通JavaScript语句慢得多,尤其是对DOM进行增删改操作,每一次对DOM的修改都会引起浏览器对网页的重新布局和重新渲染,而这个过程是很耗时的。这也是为什么前端性能优化中有一条原则:尽量减少DOM操作。
既然操作DOM效率低下,那么有什么办法可以解决这个问题呢?在软件开发中,有这么一句话:软件开发中遇到的所有问题都可以通过增加一层抽象而得到解决。DOM效率低下的问题同样可以通过增加一层抽象解决。虚拟DOM就是这层抽象,建立在真实DOM之上,对真实DOM的抽象。这里需要注意,虚拟DOM并非React所独有的,它是一个独立的技术,只不过React使用了这项技术来提高自身性能。
虚拟DOM使用普通的JavaScript对象来描述DOM元素,对象的结构和React.createElement方法使用的参数的结构类似,实际上,React元素本身就是一个虚拟DOM节点。
有了虚拟DOM这一层,当我们需要操作DOM时,就可以操作虚拟DOM,而不是操作真实DOM,虚拟DOM是普通的JavaScript对象,访问JavaScript对象当然比访问真实DOM要快得多。

三十一、虚拟DOM-Diff算法

React采用声明式的API描述UI结构,每次组件的状态或属性更新,组件的render方法都会返回一个新的虚拟DOM对象,用来表述新的UI结构。如果每次render都直接使用新的虚拟DOM来生成真实DOM结构,那么会带来大量真实DOM的操作,影响程序执行效率。事实上,React会通过对比两次虚拟DOM结构的变化找出差异部分,更新到真实DOM上,从而减少最终要在真实DOM上执行的操作,提高程序执行效率。这一过程就是React的调和过程(Reconciliation),其中的关键是比较两个树形结构的Diff算法。在Diff算法中,比较的两方是新的虚拟DOM和旧的虚拟DOM,而不是虚拟DOM和真实DOM,只不过Diff的结果会更新到真实DOM上。

三十二、高阶组件

高阶组件是React中一个很重要且较复杂的概念,主要用来实现组件逻辑的抽象和复用,在很多第三方库(如Redux)中都被使用到。即使开发一般的业务项目,如果能合理地使用高阶组件,也能显著提高项目的代码质量。
在JavaScript中,高阶函数是以函数为参数,并且返回值也是函数的函数。类似地,高阶组件(简称HOC)接收React组件作为参数,并且返回一个新的React组件。高阶组件本质上也是一个函数,并不是一个组件。高阶组件的函数形式如下:

const  EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件的主要功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用。高阶组件的这种实现方式本质上是装饰者设计模式。
高阶组件的使用场景主要有以下4种:

  • 操纵props
  • 通过ref访问组件实例
  • 组件状态提升
  • 用其他元素包装组件

三十三、单页面应用和前端路由

在传统的Web应用中,浏览器根据地址栏的URL向服务器发送一个HTTP请求,服务器根据URL返回一个HTML页面。这种情况下,一个URL对应一个HTML页面,一个Web应用包含很多HTML页面,这样的应用就是多页面应用;在多页面应用中,页面路由的控制由服务器端负责,这种路由方式称为后端路由。
在多页面应用中,每次页面切换都需要向服务器发送一次请求,页面使用到的静态资源也需要重新请求加载,存在一定的浪费。而且,页面的整体刷新对用户体验也有影响,因为不同页面往往存在共同的部分,例如导航栏、侧边栏等,页面整体刷新也会导致共用部分的刷新。
有没有一种方式让Web应用只是看起来像多页面应用,实际URL的变化可以引起页面内容的变化,但不会向服务器发送新的请求呢?实际上,满足这种要求的Web应用就是单页面应用(Single Page Application,简称SPA)。单页面应用虽然名为“单页”,但视觉上的感受仍然是多页面,因为URL发生变化,页面的内容也会发生变化,但这只是逻辑上的多页面,实际上无论URL如何变化,对应的HTML文件都是同一个,这也是单页面应用名字的由来。在单页面应用中,URL发生变化并不会向服务器发送新的请求,所以“逻辑页面”(这个名称用来和真实的HTML页面区分)的路由只能由前端负责,这种路由方式称为前端路由。
React Router 就是一种前端路由的实现方式。通过使用React Router可以让Web应用根据不同的URL渲染不同的组件,这样的组件渲染方式可以解决更加复杂的业务场景。例如,当URL的pathname 为/list时,页面会渲染一个列表组件,当点击列表中的一项时,pathname更改为/item/:id(id为参数),旧的列表组件会被卸载,取而代之的是一个新的单一项详情组件。

三十四、Redux可预测的状态管理机

React主要的关注点是如何创建可复用的视图层组件,对于组件之间的数据传递和状态管理并没有给出很好的解决方案,所以,当我们创建大型Web应用时,只使用React往往不能高效、清晰地对应用和组件状态进行管理,这时候就需要引入额外的类库完成这项工作,Redux就是其中的一个代表。Redux的思想继承自Facebook官方的Flux架构,但比Flux更加简洁易用。

你可能感兴趣的:(《React进阶之路》笔记)