React知识学习笔记

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>
}

注意事项

  1. 组件名称首字母必须大写,用于区分组件和普通标签
  1. jsx语法外层必须又一个跟元素

1.3.3. 组件的props

1.3.3.1. props传递数据

在调用组件时可以向组件内部传递数据,在组件中可以通过props对象获取外部传递进来的数据。

注意:

  1. props对象中存储的数据是只读的,不能在组件内部修改
  2. 当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. 单向数据流
  1. 在React中,关于数据流动有一条原则,就是单项数据流动,字顶向下,从父组件到子组件
  2. 单向数据流特性要求我们共享数据要放置在上层组件中
  3. 子组件通过调用父组件传递过来的方法更改数据
  4. 当数据发生更改时,React会重新渲染组件数
  5. 单项数据流使组件之间的数据流动变得可预测。使得定位程序错误变得简单。

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. 解决方案

  1. 利用浏览器空闲时间执行任务,拒绝长时间占用主线程。
  2. 放弃递归只采用循环,因为循环可以被中断
  3. 任务拆分,将任务拆分成一个个的小任务

3.3.3. 实现思路

在Fiber方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:

  1. 构建 Fiber (可中断)
  2. 提交 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链表结构图
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值