React 的特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
React 为什么高效?
- 使用了虚拟 DOM,不总是直接操作页面真是 DOM
- DOM Diffing 算法,最小化页面重绘
虚拟 DOM 与真实 DOM
虚拟 DOM 本质上是一个 Object 对象,最终会被 React 转换为真实 DOM,渲染到界面上。
虚拟 DOM 比较轻量。
两种方式创建虚拟 DOM
使用原生 JS(不推荐):
1 | const VDOM = React.createElement('h1', {id: 'title'}, 'hello world') |
使用 JSX(会通过 Babel 转化为 JS):
1 | const VDOM = <h1 id='title'>hello world</h1> |
渲染虚拟 DOM
将虚拟 DOM 渲染到页面中的真实容器 DOM 里
1 | React.render(VDOM, containerDOM) |
JSX 语法规则
- 定义虚拟DOM时,不用写引号
- 标签中混入JS表达式时要用
{}
- 样式的类名指定要用
className
- 内联样式要用
style = {{key:value}}
的形式,使用驼峰命名法 - 虚拟 DOM 只能有一个根标签
- 标签必须闭合
- 标签首字母
- 小写字母开头,则将该标签转为 html 中的同名元素
- 大写字母开头,React 渲染对应的组件
面向组件编程
函数式组件
1 | function Demo() { |
类式组件
类式组件必须继承 React.Component
,且具有 render()
方法
1 | class Demo extends React.Component { |
原理: 当执行 ReactDOM.render(<Demo/>.......)
之后,React 解析组件标签,找到了 Demo 组件。发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法。将 render
返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
组件实例的三大核心属性
state
state
属性用来表示组件的状态
state
属性只能通过 setState
函数更改,会与原先的 state
合并
state 示例
1 | class Weather extends React.Component { |
构造器中的 this
指向当前类的示例对象,render
方法通过类的实例调用,方法中的 this
也只想当前类的实例对象。
change
方法作为 onClick
的回调,并不是由类的实例调用,是直接调用。类中定义的方法自动开启了局部严格模式,故 this
为 undefined
可以通过 bind
函数将当前实例对象绑定到 change
上
state 的简写方式(推荐)
一般类组件中的自定义方法(除了 render
)是作为回调函数调用的,可以将方法写成赋值语句+箭头函数的形式。箭头函数会找它外层函数的 this
作为它本身的 this
。
1 | class Weather extends React.Component { |
props
用于向组件传递数据,是只读的。
类式组件使用 props
1 | class Person extends React.Component { |
类式组件的构造器是否接收 props
,是否传递给 super
,取决于是否希望在构造器中通过 this
访问 props
。一般情况下都不写构造器。
函数式组件使用 props
函数式组件通过接收参数来传递 props
1 | function Person(props) { |
refs 与事件处理
ref
用来标识某个标签,相当于原生标签中的 id,可以通过 this.refs
来获取到对应的真实 DOM
字符串形式的 ref
存在效率问题,不推荐使用
1 | class Demo extends React.Component { |
回调形式的 ref
ref
传入一个回调函数,回调函数的参数就是当前 DOM 节点,React 会自动调用该回调函数,将该节点挂载到实例对象上。
1 | class Demo extends React.Component { |
关于回调 refs 的说明
如果ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的ref
并且设置新的。通过将ref
的回调函数定义成class
的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
使用 createRef
使用 React.createRef
API 来创建一个容器,该容器可以存储被 ref 所标识的节点,一个容器只能放一个节点
1 | class Demo extends React.Component { |
注意事项
避免过度使用 ref
,建议使用受控组件,类似 Vue 中的双向绑定。
1 | class Demo extends React.Component { |
生命周期
React 组件中包含一系列生命周期回调函数,会在特定的时刻调用
static getDerivedStateFromProps
1 | static getDerivedStateFromProps(props, state) |
从 props
中获得派生的状态,适用于 state
值在任何时候都取决于 props
,一般不建议使用。
getSnapshotBeforeUpdate
1 | getSnapshotBeforeUpdate(prevProps, prevState) |
在最近一次渲染输出之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息。其返回值将作为参数传递给 componentDidUpdate()
DOM 的 Diffing 算法
在虚拟 DOM 渲染到页面上成为真实 DOM 之前会与旧的虚拟 DOM 进行对比,如果没有差异,就不会更新原先的真实 DOM,反之将虚拟 DOM 重新渲染到页面上。
虚拟 DOM 中的 key 的作用
key 是虚拟 DOM 对象的标识
当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟 DOM】,随后React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
- 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,根据数据创建新的真实 DOM,随后渲染到到页面
用 index 作为 key 可能会引发的问题:
- 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新。虽然界面效果没问题, 但效率低。
- 如果结构中还包含输入类的 DOM,会产生错误 DOM 更新,界面有问题。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。