React高级进阶教程

对React的理解深度,将决定能解决的实战问题复杂度的上限

  1. 深挖一个优质的前端框架,吃透其底层原理
  2. 跟随框架作者(React团队)学习架构思想、学编码规范、学设计模式

先提现象/问题,再挖原理



JSX如何转变为DOM?

JSX是JS的一种语法扩展,它和模板语言很接近,充分具备JS的能力
浏览器不会天然支持JSX,JSX会被编译为React.createElement(),React.createElement()将返回一个叫做"React Element"的JS对象

什么是Babel?

Babel是一个工具链,主要用于将ECMAScript2015版本的代码转换为向后兼容的JS语法,以便能够运行在当前和旧版本的
浏览器或其他环境中

var name = 'Guy Fieri'
var place = 'Wangzz'

`Hello ${name}, ready for ${place}?`



createElement => 本质上“数据处理层”可以看作是一个数据转换器将JSX转换为真实DOM


React.render()
ReactDOM.render(
  // 需要渲染的元素(ReactElement)
  element,
  // 元素挂载的目标容器(一个真实DOM)
  container,
  // 回调函数,可选参数,可用来处理渲染结束后的逻辑
  [callback]
)

<body>
  <div id='root'></div>
</body>
const rootElement = document.getElementById('root')
ReactDOM.render(<App/>, rootElement)

React生命周期

初步理解React框架中的一些关键的设计思想

"组件" & "虚拟DOM"

虚拟DOM:核心算法的基石

组件化:工程化思想在框架中的落地

所有的可见/不可见的内容都可以被抽离为各种各样的组件,每个组件即是"封闭"的也是"开放"的

"封闭" 针对“渲染工作流”来说的,在组件自身的渲染工作流中每个组件都只处理其内部的渲染逻辑

"开放" 针对"组件间通信"来说的,React允许开发者基于"单向数据流"的原则完成组件之间的通信
而组件之间的通信又将改变通信双方/某一方内部的数据进而对渲染结果构成影响

生命周期方法的本质:组件的“灵魂”和“躯干”
将render方法形容为React组件的“灵魂”

"渲染工作流": 指的是从组件数据改变到组件实际更新发生的过程

render之外的生命周期方法可理解为组件的“躯干”

React15的生命周期流程
constructor()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillMount()
componentWillUpdate()
componentDidUpdate()
componentDidMount()
render()
componentWillUnmount()

  • Mounting阶段:组件的初始化渲染(挂载
constructor(props) {
  super(props)
  this.state = {text: '子组件的文本'}
}

render在执行过程中并不会去操作真实DOM,其职能是把需要渲染的内容返回出来

componentDidMount方法在渲染结束后被触发
真实DOM已经挂载到页面上,可在这个生命周期里执行真实DOM相关的操作

进入constructor
componentWillMount方法执行
render方法执行
componentDidMount方法执行
  • Upadting阶段:组件的更新
    componentWillReceiveProps
    是在组件的props内容发生变化时被触发的

如果父组件导致组件重新渲染,即使props没有更改也会调用此方法(componentReceiveProps)
如果只想处理更改,请确保进行当前值和变更值得比较

🥑🥑🥑conponentReceiveProps并不是由props的变化触发的,而是由父组件的更新触发

React组件会根据shouldComponentUpdate的返回值来决定是否执行该方法之后的生命周期
进而决定是否对组件进行re-render(重渲染)

  • Unmounting阶段:组件的卸载

componentWillUnmount

废弃componentWillMount,新增了getDrivedStateFromProps - v16.3

React16对render方法进行改进,React16之前render方法必须返回单个元素
而React16允许返回元素数组和字符串

getDerivedStateFromProps不是componentWillMount的替代品
componentWillMount被废弃

getDerivedStateFromProps有且仅有一个用途:使用props来派生/更新state

// 初始化/更新时调用
static getDerivedStateFromProps(props, state) {
  return {
     fatherText: props.text
  }  
}

// 初始化渲染时调用
componentDidMount() {}

// 组件更新时调用
shouldComponentUpdate(nextProps, nextState) {}

getDerivedStateFromProps方法对state的更新动作并非“覆盖”式的更新
而是针对某个属性的定向更新

getDerivedStateFromProps可代替componentWillReceiveProps实现基于props派生state

消失的componentWillUpdate和新增的getSnapshotBeforeUpdate

实现一个内容会发生变化的滚动列表要求根据滚动列表的内容是否发生变化来决定是否要记录滚动条的当前位置

// 组件更新时调用
getSnapshotBeforeUpdate(prevProps, prevState) {
  console.log("getSnapshotBeforeUpdate方法执行")
  return 'haha'
}

// 组件更新后调用
componentDidUpdate(prevProps, prevState, valueFromSnapshot) {
  
}

Fiber是React16对React核心算法的一次重写

Fiber会使原本同步的渲染过程编程异步的

Fiber将一个大的更新任务拆建为许多个小任务

生命周期工作流

Fiber架构的重要特征:可以被打断的异步渲染模式
根据“能否被打断”的这一标准,React16的生命周期被划分为render和commit两个阶段

render阶段是允许暂停、终止和重启的
导致render阶段的生命周期有可能被重复执行

* componentWillMount
* componentWillUpdate
* componentWillReceiveProps

数据是如何在React组件之间流动的?

UI = render(data) OR UI = f(data)

React视图随着数据的变化而变化

基于props的单向数据流

组件,从概念上类似于JS函数,接收任意的入参(即props)并返回用于描述页面展示内容的React元素

单向数据流 =》 当前组件的state以props的形式流动时只能流向组件树中比自己层级更低的组件

  • 父-子组件通信

React的数据流是单向的父组件可直接将this.props传入子组件实现 父-子之间的通信

父组件传递给子组件是一个绑定了具有自身上下文的函数,则子组件在调用该函数的时候
就可以将想要交给父组件的数据以函数入参的形式给出去

Redux数据管理

React-Hooks设计动机

React-Hooks:是React团队在React组件开发时间中逐渐认知到的一个改进点,背后设计对类组件和函数组件两种组件形式的思考和 侧重

  • 类组件(Class Component)

  • 函数组件/无状态组件(Function Component/Stateless Component)

function DemoFunction(props) {
  const {text} = props
  return (
    <div className="demoFunction">
      <p>{`function 组件所接收到的来自外界的文本内容是:[${text}]`}</p>
    </div>
  )
}

函数组件和类组件的对比:无关“优劣”,只谈“不同”

  • 类组件需要继承class,函数组件不需要
  • 类组件可访问生命周期方法,函数组件不能
  • 类组件中可以获取到实例化后的this,并基于这个this做各种各样的事情,而函数组件不行
  • 类组件可定义并维护state(状态),而函数组件不可以

函数组件和类组件的对比:“无关优劣,只谈不同”

在React-Hooks出现之前的世界里类组件的能力边界明显强于函数组件
类组件:包裹在面向对象思想下的“重装战舰”
类组件:面向对象编程思想的一种表征

封装:将一类属性和方法,“聚拢”到一个class里去
继承:新的class可以通过继承现有class,实现对某一类属性和方法的复用

class DemoClass extends React.Component {
  // 初始化类组件的state
  state = {
    txt: ''
  }

  // 编写生命周期方法 didMount
  componentDidMount() {
    
  }

  // 编写自定义的实例方法
  changeTxt = (newTxt) => {
    // 更新state
    this.setState({
      text: newText
    })
  }  

  // 编写生命周期方法 render
  render() {
    return {
      <div className="demoClass">
        <p>{this.state.text}</p>
        <button onClick={this.changeText}>点我修改</button>
      </div>
    }
  }
}

React类组件提供了多少东西,就需要学多少东西,“大而全”的背后,是不可忽略的学习成本

对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势,复杂的姿势也必然带来高昂的理解成本(心智负担)

开发者编写的逻辑在封装后是和组件站在一起的,使得类组件内部的逻辑难以实现拆分和复用

深入理解函数组件:呼应React设计思想的“轻巧快艇”
function DemoFunction(props) { 
  const {text} = props
  return (
    <div className="demoFunction">
      <p>{`function 组件所接收到的来自外界的文本内容是:[${text}]`}</p>
    </div>
  )
}

函数组件会捕获render内部的状态,这是两类组件最大的不同

类组件和函数组件之间,纵有千差万别但最不能够被我们忽视掉是心智模式层面的差异

函数组件更加契合React框架的设计理念

UI = render(data)
  OR
UI = f(data)

React组件本身的定位就是函数,一个吃进数据、吐出UI的函数
React将声明式的代码转换为命令式的DOM操作,将数据层面的描述映射到用户可见的UI变化中去
函数组件 ======》 会捕获render内部的状态,这是两类组件最大的不同

虽然props本身是不可改变的,但是this却是可变的,this上的数据是可以被修改的

函数组件会捕获render内部的状态 =====》 函数组件真正地把数据和渲染绑定到了一起

函数组件是一个更加匹配其设计理念,也更有利于逻辑拆分和重用的组件表达形式

Hooks的本质:一套能够使函数组件更强大、更灵活的“钩子”

函数组件比起类组件“少”了很多东西,给函数组件的使用带来了非常多的局限性

如果说函数组件是一台轻巧的快艇,那么React-Hooks就是一个内容丰富的零部件箱
允许自由地选择和使用你需要的哪些能力

React-Hooks设计动机和工作模式

构建对React-Hooks的整体认知,通过一系列的编码实例来认识useState、useEffect两个有代表性的Hook

  • useState(): 为函数组件引入状态

早期的函数组件相比于类组件,其中的一大劣势是缺乏定义和维护state的能力
useState是一个能够为函数组件引入状态的API

useState返回的是一个数组,
数组的第一个元素对应的是我们想要的那个state变量
数组的第二个元素对应的是能够修改这个变量的API

const [state, setState] = useState(initialState)

setState(newState)  - update value

// 状态和修改状体的API名都是可自定义的

调用React.useState的时候,实际上是给这个组件关联了一个状态

  • useEffect(): 允许函数组件执行副作用操作

useEffect在一定程序上弥补生命周期的缺席

useEffect能够为函数组件引入副作用,过去习惯放在componentDidMount、componentDidUpdate和componentWillUnmount三个生命周期中做的事情
现在可以放在useEffect中去做

useEffect(() => {}) === componentDidMount\componentDidUpdate
// useEffect是用于为函数组件引入副作用的钩子
  • 🧃 useEffect(callback): 每次渲染后都执行的副作用:传入回调函数,不传依赖数组

  • 🥑 useEffect(callback, []): 仅仅在挂载阶段执行一次的副作用

  • 🔴 仅在挂载阶段和卸载阶段执行的副作用,传入回调函数而且这个函数的返回值是一个函数,同时传入一个空数组

useEffect(() => {
  // A的业务逻辑

  // 返回一个函数记为B
  return () => { // 🧹 useEffect回调中返回的函数被称为“清除函数”

  }
}, [])


















React中的“栈调和(Stack Recondciler) 的过程是什么样的?












11 => setState到底是同步的,还是异步的?

setState相关面试题




setState工作流





★★★ 如何理解Fiber架构的迭代动和设计思想?


Stack Recondciler到底有着怎么样根深蒂固的局限性?

















ReactDOM.render是如何串联渲染链路的?












ReactDOM.render是如何串联渲染链路的?










posted @ 2024-06-30 23:56  Felix_Openmind  阅读(435)  评论(0)    收藏  举报
*{cursor: url(https://files-cdn.cnblogs.com/files/morango/fish-cursor.ico),auto;}