1. React基础知识
1.1. 介绍
React是一个用于构建用户界面的JavaScript库,它只负责应用的视图层,帮助开发人员构建快速且交互式的web应用程序。
React使用组件的方式构建用户界面。
1.2. JSX语法
在React中使用JSX语法描述用户界面,它是一种Javascript语法扩展。
在React代码执行之前,Babel会讲JSX语法转化为标准的JavsScript API。
JSX语法就是一种语法糖(React.createElement),让开发人员使用更加舒服的代码构建用户界面。
1.2.1. 在JSX中使用表达式
const user = {
firstName: 'long',
lastName: 'wang'
}
function formatName (user) {
return user.firstName + ' ' + user.lastName
}
const element = <h1>Hello,{fromartName(user)}</h1>
JSX本身也是一种表达式,将它赋值给变量,当作为参数传入,作为返回值都可以
function getGreeting (user) {
if (user) {
return <h1>Hello,{fromartName(user)}</h1>
}
return <h1>Hello, Stranger</h1>
}
1.2.2. 属性
如果属性值为字符串类型,需要加引号,属性名称推荐采用驼峰式命名
const element = <div greeting="hello"></div>
如果属性值为JavaScript表达式,属性值外面大括号
const element = <img src={user.avatarUrl} />
1.2.3. JSX单标记必须合并
如果JSX是单标记,必须闭合,否则报错
const element = <img src={user.avatarUrl} />
1.2.4. className
为JSX标记添加类名需要使用className,而不是class
const element = <img src={user.avatarUrl} className="rounded" />
1.2.5. JSX自动展开数组
const arr = [<p>he</p>,<p>xi</p>,<p>hei</p>]
const element = (<div>{arr}</div>)
// 解析后
// <div>
// <p>he</p>
// <p>xi</p>
// <p>hei</p>
// </div>
1.2.6. 三元运算符
{ boolean ? <div>Hello React</div> : null }
{ boolean && <div>Hello React</div> }
1.2.7. 循环
const persons = [
{
id: 1,
name: '张三'
},
{
id: 2,
name: '李四'
},
]
<ul>
{ persons.map( person => <li key={person.id}>{person.name}</li> ) }
</ul>
1.2.8. 事件
{/* 不第一个参数即事件对象 需要传递参数 */}
<button onClick={this.eventHandler}>button</button>
{/* 需要传递事件对象 */}
<button onClick={ e => this.eventHandler('arg', e) }>button</button>
{/* 最后一个参数即事件对象 不需要传递 */}
<button onClick={this.eventHandler.bind(null, 'arg')}>button</button>
改变函数内部this指向
constructor() { //推荐使用
this.eventHander = this.eventHandler.bind(this)
}
eventHandler() {
<button onClick={this.eventHandler}>button</button>
}
1.2.9. 样式
1.2.9.1. 行内样式
class App extends Component {
render() {
const style = {width: 200, height: 200, backgroundColor: 'red'}
return <div style={style}></div>
}
}
1.2.9.2. 外链样式
//Button.js
import styles from './Button/module.css'
class Button extends Component {
render() {
return <button className={style.error}>err button</button>
}
}
1.2.9.3. 全局样式
import './styles.css'
1.2.10. ref属性
1.2.10.1. createRef
class Input extends Component {
constructor() {
super()
this.inputRef = React.createRef()
}
render() {
return (
<div>
<input type="text" ref={this.inputRef} />
<button onClick={() => console.log(this.inputRef.current)}>button</button>
</div>
)
}
}
1.2.10.2. 函数参数
class Input extends Component {
render() {
return (
<div>
<input type="text" ref={input => (this.input = input)} />
</div>
)
}
}
1.2.10.3. ref字符串
不推荐使用 在严格模式下报错
class Input extends Component {
render() {
return (
<div>
<input type="text" ref="username" />
<button onClick={() => console.log(this.refs.username)}>button</button>
</div>
)
}
}
1.2.10.4. 获取组件实例
点击按钮让input文本框获取焦点
input文本框以及让文本框获取焦点的方法定义在input组件中,在App组件中引入Input组件,按钮定义在App组件中
// Input.js
class Input extends Component {
constructor() {
super()
this.inputRef = React.createRef()
this.focusInput = this.focusInput.bind(this)
}
focusInput() {
this.inputRef.current.focus()
}
render() {
return (
<div>
<input type="text" ref={this.inputRef}></input>
</div>
)
}
}
//App.js
class App extends Component {
constructor() {
super()
this.InputComponentRef = React.createRef()
}
render() {
return (
<div className="App">
<Input ref={this.InputComponentRef} />
<button onClick={() => this.InputComponentRef.current.focusInput()}>button</button>
</div>
)
}
}
1.3. 组件
1.3.1. 什么是组件
React是基于组件的方式进行用户界面开发的,组件可以理解为对页面中某一块区域的封装。
1.3.2. 创建组件
1.3.2.1. 创建类组件
import React, { Component } from 'react'
class App extends Component {
render() {
return <div>hello class component</div>
}
}
1.3.2.2. 创建函数组件
const Person = () => {
return <div>hello function component</div>
}
注意事项
- 组件名称首字母必须大写,用于区分组件和普通标签
- jsx语法外层必须又一个跟元素
1.3.3. 组件的props
1.3.3.1. props传递数据
在调用组件时可以向组件内部传递数据,在组件中可以通过props对象获取外部传递进来的数据。
注意:
- props对象中存储的数据是只读的,不能在组件内部修改
- 当props数据源中的数据被修改后,组件中的接受到的props数据会被同步更新。(数据驱动DOM)
1.3.3.2. 设置props默认值
class App extends Component {
static defaultProps = {}
}
function ThemeButton (props) {
}
ThemeButton.defaultProps = {
theme: 'red',
label: 'button text'
}
1.3.3.3. 组件children
通过props.children属性可以获取到在调用时填充到组件标签内部的内容。
<Person>组件内部的内容</Person>
const Person = (props) => {
return (
<div>{props.children}</div>
)
}
1.3.3.4. 单向数据流
- 在React中,关于数据流动有一条原则,就是单项数据流动,字顶向下,从父组件到子组件
- 单向数据流特性要求我们共享数据要放置在上层组件中
- 子组件通过调用父组件传递过来的方法更改数据
- 当数据发生更改时,React会重新渲染组件数
- 单项数据流使组件之间的数据流动变得可预测。使得定位程序错误变得简单。
1.3.4. 类组件状态state
1.3.4.1. 定义组件状态
类组件除了能够从外部(props)接收状态数据以外还可以拥有自己的状态(state),此状态在组件内部可以被更新。
组件内部的状态数据被存储在组件类中的state属性中,state属性值为对象类型,属性名称固定不可更改。
class App extends Component {
constructor() {
super()
this.state = {
person: { name: 'long', age: 20 }
}
}
render() {
return (
<div>
{ this.state.person.name }
{ this.state.person.age }
</div>
)
}
}
1.3.4.2. 更改组件状态
state状态对象中的数据不可以直接更改,如果直接更改DOM不会更新,要更改state状态数据需要使用setState方法
this.stateState({
person: {
name: 'wang',
age: 18
}
})
1.3.4.3. 双向数据绑定
双向数据绑定是指,组件类中更改了状态,DOM状态同步更新,DOM更改可状态,组件类中同步更新。组件<=>视图。
要实现双向数据绑定需要用到表单元素和state状态对象。
class App extends Component {
constructor() {
super()
this.state = {
name: 'zhang'
}
this.nameChanged = this.nameChanged.bind(this)
}
nameChanged(event) {
this.setState({
name: event.target.value
})
}
render() {
return (
<div>
<div>{this.state.name}</div>
<Person name={this.state.name} changed={this.nameChanged} />
</div>
)
}
}
const Person = props => {
return <input type="text" value={props.name} onChange={props.changed} />
}
1.3.5. 类组件的生命周期函数
1.3.5.1. Mounting
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
1.3.5.2. Updating
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate(组件更新之前需要做某种逻辑或计算)
- componentDidUpdate
1.3.6. Unmount
- componentWillUnmount
1.3.7. Context
通过Context可以跨层级传递数据
//userContext.js
import React from 'react'
const userContext = React.createContext('default value')
const UserProvider = userContext.Provider()
const UserConsumer = userContext.Consumer()
export {
UserProvider,
UserConsumer
}
//App.js
import { UserProvider } from './userContext'
class App extends Component {
render() {
<UserProvider value="hello React Context">
<A />
</UserProvider>
}
}
// A.js
import { UserConsumer } from './userContext'
export class A extends Component {
render() {
return (
<div>
<UserConsumer>
{
username => (<div>{username}</div>)
}
</UserConsumer>
</div>
)
}
}
1.4. 表单
1.4.1. 受控表单
表单控件中的值由组件的state对象来管理,state对象中存储的值和表单控件中的值是同步状态的
class App extends Component {
constructor() {
this.state = {
username: '',
}
this.nameChanged = this.nameChanged.bind(this)
}
nameChanged(e) {
this.setState({
username: e.target.value
})
}
render() {
return (
<form>
<p>{this.state.username}</p>
<input type="text" value={this.state.username} onChange={this.nameChanged} />
</form>
)
}
}
1.4.2. 非受控表单
表单元素的值由DOM元素本身管理
class App extends Component {
constructor() {
this.onSubmit = this.onSubmit.bind(this)
}
onSubmit(e) {
console.log(this.username.value)
e.preventDefault()
}
render(
<form onSubmit={this.onSubmit}>
<input type="text" ref={username => this.username = username} />
</form>
)
}
1.5. 路由
url地址与组件之间的对应关系,访问不同的url地址显示不同的组件。
npm install react-route-dom
1.5.1. 路由基本使用
//App.js
import React from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
function Index() {
return <div>首页</div>
}
function News() {
return <div>新闻</div>
}
function App() {
return (
<Router>
<div>
<Link to="/index">首页</Link>
<Link to="/news">新闻</Link>
</div>
<div>
<Route path="/index" component={Index} />
<Route path="/news" component={News} />
</div>
</Router>
)
}
1.5.2. 路由嵌套
function News(props) {
return (
<div>
<div>
<Link to={`${props.match.url}/company`}>公司新闻</Link>
<Link tp={`${props.match.url}/industry`}>行业新闻</Link>
</div>
<div>
<Route path={`${props.match.path}/company`} component={CompanyNews} />
<Route path={`${props.match.path}/industry`} component={IndustryNews} />
</div>
</div>
)
}
function CompanyNews() {
return <div>公司新闻</div>
}
function IndustryNews() {
return <div>行业新闻</div>
}
1.5.3. 路由传参
import url from 'url'
class News extends Component {
constructor(props) {
super(props)
this.state = {
list: [
{
id: 1,
title: '新闻1'
},
{
id: 2,
title: '新闻2'
},
]
}
}
render() {
return (
<div>
<div>新闻列表组件</div>
<ul>
{
this.state.list.map((item, index) => {
return (
<li key={index}>
<Link to={`/detail?id=${item.id}`}>{item.title}</Link>
</li>
)
})
}
</ul>
</div>
)
}
}
class Detail extends Component {
constructor(props) {
super(props)
}
const { query } = url.parse(this.props.location.search, true)
console.log(query) // {id: 1}
render() {
return <div>新闻详情</div>
}
}
1.5.4. 路由重定向
import { Redirect } from 'react-router-dmo'
class Login extends Component {
render() {
if (this.state.isLogin) {
return <Redirect to="/" />
}
}
}
2. React Virtual DOM以及Diff算法
2.1. JSX到底是什么
JSX语法为了让React开发人员编写用户界面代码更加轻松。
React.createElement()用来创建Virtual DOM
2.2. DOM操作问题
大多数JavaScript框架对于DOM的更新远远超过其必须的更新,从而使得这种缓慢的操作变得更糟。
Virtual DOM出现的目的就是为了提高JavaScript操作DOM对象的效率。
2.3. 什么是Virtual DOM
在React中,每个DOM对象都有一个对应的Virtual DOM对象,它是DOM对象的JavaScript对象表现形式,其实就是使用JavaScript对象来描述DOM对象信息。比如DOM对象的类型是什么,它身上有哪有属性,它拥有哪些子元素。
<div className="container">
<h3>Hello React</h3>
<p>React is great </p>
</div>
{
type: "div",
props: { className: "container" },
children: [
{
type: "h3",
props: null,
children: [
{
type: "text",
props: {
textContent: "Hello React"
}
}
]
},
{
type: "p",
props: null,
children: [
{
type: "text",
props: {
textContent: "React is great"
}
}
]
}
]
}
2.4. Virtual DOM如何提升效率
精准找出发生变化的DOM对象,只更新发生变化的部分。
在React第一次创建DOM对象后,会为每个DOM对象创建其对应的Virtual对象,在DOM对象发生更新之前,React会先更新所有的Virtual DOM对象,然后React会将更新后的Virtual DOM和更新前的Virtual DOM进行比较,从而找出发生变化的部分,React会将发生变化的部分更新到真实的DOM对象中,React仅更新必要更新的部分。
2.5. 创建Virtual DOM
在React代码执行前,JSX会被Babel转换为React.createElement
方法的调用,在调用createElement
方法时会传入元素的类型,元素的属性以及元素的子元素,crateElement
方法的返回值为构建好的Virtual DOM
对象。
{
type: 'div',
props: null,
children: [{type: 'text', props: {textContent: 'hello'}}]
}
2.6. 渲染VIrtual DOM对象为DOM对象
调用render方法
2.7. 为元素节点添加属性
- addEventListener
- setAttribute
2.8. 渲染组件
2.8.1. 函数组件
type: ‘function’
2.8.2. 类组件
render方法
2.9. Virtual DOM对比
2.10. ref 属性
2.11. key 属性
3. Fiber
3.1. requireIdleCallback
3.1.1. 核心API功能介绍
利用浏览器的空余时间执行任务,如果有更高优先级的任务执行,当前任务可以被终止,执行优先级高级别的任务。
requestIdleCallback(function(deadline) {
// deadline.timeRemaining() // 获取浏览器的空余时间
})
3.2. 浏览器空余时间
页面是一桢一桢绘制出来的,当每秒绘制数达到60时,页面是流畅的,小于这个值时,用户会感觉到卡顿。
1s 60桢,每一帧分到时间是 1000 / 60 ≈ 16 ms,如果每一帧执行的时间小于16ms,就说明浏览器有空余时间。
如果任务在剩余的时间内没有完成则会停止任务执行,继续优先执行主任务,也就是说 requestIdleCallback 总是利用浏览器的空余时间执行任务
3.3. Fiber
3.3.1. 问题
React 16 之前的版本更新VirtualDOM的过程是采用递归实现的,这种比对方式有一个问题,就是任务一旦开始进行就无法中断,如果应用中组件数量庞大,主线程被长时间占用,直到整颗VirtualDOM树比对更新完成之后主线程才被释放,主线程才能执行其他任务。这就会导致一些用户交互,动画等任务无法立即得到执行,页面就会产生卡顿,影响用户体验。
核心问题:递归无法中断,执行重任务耗时长。JavaScript又是但页面线程,无法同时执行其他任务,导致任务延迟页面卡顿,用户体验差。
3.3.2. 解决方案
- 利用浏览器空闲时间执行任务,拒绝长时间占用主线程。
- 放弃递归只采用循环,因为循环可以被中断
- 任务拆分,将任务拆分成一个个的小任务
3.3.3. 实现思路
在Fiber方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:
- 构建 Fiber (可中断)
- 提交 Commit (不可中断)
DOM 初始渲染:virtualDOM -> Fiber -> Fiber[] -> DOM
DOM 更新操作: newFiber vs oldFiber -> Fiber[] -> DOM
3.3.4. Fiber对象
{
type 节点类型(元素 | 文本 | 组件)
props 节点属性
stateNode 节点DOM对象 | 组件实例对象
tag 节点标记(hostRoot | hostComponent | classComponent | functionComponent)
effects 数组,存放需要更改的 fiber 对象
effectTag 当前 fiber 要被执行的操作(新增 | 删除 | 修改)
parent 当前 fiber 的父级 fiber
child 当前 fiber 的子级 fiber
sibling 当前 fiber 的下一个兄弟 fiber
alternate fiber 备份 fiber , 比对时使用
}
fiber链表结构图