IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React相关问题 -> 正文阅读

[JavaScript知识库]React相关问题


(PS:本文尚未更新完成。本文仅为个人观点或整理笔记,如有问题,望请指正)

设计理念

用javascript构建快速响应的大型web应用程序的首选方式。关键是快速响应。

为了解决快速响应的问题,面临两个问题:

? (1)I/O瓶颈

? 网络延迟前端无法解决,在网络延迟客观存在的情况下,React通过将人机交互研究的成果整合到真实UI中来降低用户对网络延迟的感知程度。

? (2)CPU瓶颈

? 由于js是单线程的,js同步执行会阻塞渲染,因此在遇到CPU密集型计算时,会导致页面卡顿,因此,为突破CPU瓶颈问题,react的做法是使用concurrent模式,启用时间切片,可以把长任务拆分到每一帧中——这个做法就是从React15更新到React16的最核心的思想的体现,也就是将同步的更新变为可中断的异步更新。

为什么用react框架(框架比原生环境有什么好处?<为什么要重构,除了学习原因>)

(1)最大限度避免直接操作DOM带来的性能问题,以及React的虚拟DOM可以在每次更新时对真实dom作最少的修改

(2)可以复用组件

(3)组件是数据驱动的,意味着组件维护自身对数据的响应就行了,容易维护,也就是说考虑把数据放在哪,而不是怎么更新dom结构

(4)组件化和模块化开发(组件化是UI层面上的拆分,模块化是逻辑层面上的拆分)

vDOM详述(包括diff原理)

? 简单来说就是用来映射真实DOM的一个js对象。虚拟DOM的核心思想是让开发者可以避免直接操作DOM,以及每次更新时只进行最小化的dom操作,以此来降低操作真实DOM带来的页面性能问题,比如重绘重排。

? 详细来说,React使用双缓存来完成VDOM树,也就是Fiber树的构建与替换,视图中对应的是current Fiber树,内存中正在构建的是workInProgress Fiber树,在内存中进行新旧Fiber树的比较,找出差异,复用current Fiber树的节点,只修改需要修改的节点。这个比较新旧Fiber树差异的算法就是diff算法。

? 比较两颗树的节点的差异,最优解也是O(n3)的算法,这在性能上是不能接受的。但是由于我们一般只在同层级DOM进行更新,所以在diff算法中,通过DFS遍历树的节点,但是只对同级元素进行比较,如果一个DOM节点在前后两次状态中跨越了层级,React不会尝试复用它,这是diff算法的第一个特点;第二个特点是如果节点类型不同,比如从div更新成了p,那么React会销毁该节点所在的子树,重新构建新状态对应的子树;第三个特点是,开发者可以为子元素指定key这个属性,用来表明哪些节点在更新中能够保持稳定,比如只是将两个元素位置对调,但是元素包含key属性,react在比较时会复用这两个节点,只是调整顺序,而不是销毁重建。

对Fiber数据结构的了解

  1. 背景:对DOM的操作非常影响性能,于是有了diff算法。尽管有了diff算法,但是在面对复杂的大型应用时,diff算法消耗的时间仍然很长。在React15中,使用diff算法对比差异的过程是stack reconciler,它是递归更新且不能中断的,这在渲染一个消耗性能的操作时仍然会导致卡顿(递归对比所需时间超过一帧的时间就会卡顿)。

  2. Fiber是什么?

    Fiber的含义需要从多个角度解释:

    1. react的理念是构建快速响应的应用,因此,在react16中,抛弃了stack reconciler,而采用fiber reconciler,并在react17中加入了lane模型,这么做的目标就是实现异步的可中断的更新,Fiber就是Fiber reconciler架构中的核心数据结构。
    2. 作为静态的数据结构:每个Fiber对应一个React Element,保存了该组件的类型(函数组件/类组件/原生组件),对应的DOM节点信息等。
    3. 作为动态的工作单元:每个Fiber保存了本次更新中该组件改变的状态、要执行的工作(被删除/插入/更新…)

    Fiber数据结构中核心的属性:

    1. 作为树结构节点的属性:return(指向父节点)、sibling(指向兄弟节点)、child(指向子节点)
    2. 作为双缓存更新策略中两棵Fiber树转换的桥梁:alternate
    3. 作为静态的数据结构的属性(没有全列出来):key、elementType(指向函数或类或DOM节点tagName)、stateNode(对应的真实DOM节点)
    4. 作为动态的工作单元:state/props状态改变相关信息,本次更新会造成的副作用(DOM操作)
    5. 保存调度优先级的属性:lanes和childLanes

setState

  1. 原理

  2. 多次setState

  3. 异步setState

组件间消息传递最佳实践

  1. 父子:props;通过子组件的refs直接使用子组件实例上的方法
  2. 子父:props传递能够修改父状态的回调函数;利用事件冒泡,把子组件的事件委托给父组件(比如获取子组件的点击事件,进而修改父状态)
  3. 父子(深层):context
  4. 兄弟:父节点为桥梁
  5. 不相关组件:pubsub,redux|react-redux|全局变量

props改变后组件内部变化顺序

  1. 类组件
    执行生命周期函数,顺序是

    ① static getDerivedStateFromProps 这个方法是挂在类上的,this是undefined,这个函数的作用是从props中派生组件的状态

    ② 然后执行类实例上的生命周期函数,shouldComponentUpdate return false的话中断更新,return true的话继续执行

    ③ 执行render方法

    ④ 在渲染前,执行getSnapshotBeforeUpdate,该函数返回值将传入componentDidUpdate

    ⑤ 进入虚拟DOM的diff算法,然后修改相应节点

    ⑥ 触发componentDidUpdate

    ⑦ 渲染到页面中

    react生命周期(新)

  2. 函数式组件

    执行函数,对于useEffect,useMemo,useCallback等钩子,如果依赖改变,就执行回调函数,否则跳过。return后进入虚拟DOM的diff算法,重新渲染到页面上。

生命周期

  1. 有哪些(见上图)
  2. 新增了哪些,删除了哪些,为什么删除

redux原理及横向库了解

redux的官方定义是一个可预测的Javascript应用状态管理容器。

核心原理是唯一数据源,数据源只读,只能通过纯函数修改数据源。相应的三个核心的概念是store,action,reducer。

(1)store可以理解为整个redux应用的所有状态的存储,或者说是全局的state,但是它没有setter,不能直接更改。它身上有三个重要的api:

store.getState() //获取store维护的state
store.dispatch(actionObj) //改变state的唯一方法
store.subscribe(fn) //订阅一个函数,每当state发生改变都会调用这个回调函数

(2)action,本质是一个包含type键的对象,实际使用过程中一般使用action creator函数,作用是返回一个action对象,比如

const Add = data=>({type:'ADD',data})

(3)通过action改变store维护的状态的桥梁就是reducer。它是一个纯函数,形式是根据之前的状态和action,进行无副作用的计算,返回一个新的状态。

(4)在redux中的数据流是,UI发出动作(比如click)->action描述发生了什么->reducer执行无副作用的计算->store更新->UI响应store的更新。(也可以独立于UI)

我在自己的项目中,使用的是react-redux的库。

  1. 这个库提供了一个Provider组件,作用是将state和dispatch(action)通过props传入Provider包含的所有组件,Provider的唯一属性是store

  2. react-redux的核心是connect函数,它接收mapStateToProps和mapDispatchToProps,返回一个函数,返回的函数接收一个组件,返回包装后的组件。作用是将state和dispatch(action)通过props传入组件。其中,

    1)mapStateToProps的作用是将store维护的state根据组件的需要,传入部分state作为组件的props

    2)mapDispatchToProps是包含组件需要用到的action对象的集合,可以是返回action的函数,也可以是一个object,它是action creator的集合,用于在组件内部向store dispatch action触发store的更新

    1. connect函数会在传入组件的componentDidMount中注册store状态变化的监听,在componentWillUnmount中注销

redux优缺点

(1)缺点:

? ① 复杂,冗余,一个功能要在reducer、action、actionType多个文件中写,加上js是弱类型语言,更加容易出bug

? ② 需要中间件来支持副作用操作

? ③ store的设计,在状态层级比较深的时候,修改起来比较麻烦和容易出问题

(2)优点:

? redux标榜的最大的优势是pure,也就是predictable,可预测。这在一些场景下有优势,比如,触发bug的时候,可以方便开发者根据action序列重现bug。再比如需要全局的状态,数据不是单方向流动等情况。

useState等钩子原理

(可以考虑从这个角度回答为什么useHooks只能放在函数的顶层作用域)

(1)[state,setState] = useState(initState)

var _state=[]
var _index=0
function useState(initState){
    const current_index = _index
    _state[current_index] = _state[current_index]===undefined?initState:_state[current_index]
    const setState=(newValue)=>{
        _state[current_index] = newValue
        render()
    }
     _index++ /*!!!important*/
    return [state[current_index],setState]
}

function render(){
    _index=0 /*!!!important*/
	ReactDOM.render(<App/>,document.getElementById('root'))    
}

(2)useEffect(callback,deps)

useEffect 在依赖变化时,执行回调函数。这个变化,是「本次 render 和上次 render 时的依赖比较」。

我们需要:

  • 存储依赖,上一次 render 的依赖
  • 兼容多次调用,也是收纳盒的思路
  • 比较依赖,执行回调函数
  • (如果有)执行回调函数中return的回调函数,清除上一次的副作用
const lastDepsBox = []
const lastClearCallbackBox = []
var _index = 0

const useEffect = (callback,deps)=>{
    //上一次的依赖
    const lastDeps = lastDepsBox[_index]
    const changed = 
          !lastDeps //首次渲染 lastDeps为undefined
    	  || !deps  //不传入deps的情况
    	  || deps.some((dep,i)=>dep!==lastDeps[i])
    if(changed){
        lastDepsBox[_index] = deps //保存新的依赖
        //上一次的清除副作用的回调函数
        const lastClearCallback = lastClearCallbackBox[_index]
        if(typeof lastClearCallback==='function'){
            lastClearCallback()
        }
        lastClearCallbackBox[_index] = callback() //执行callback,并保存返回结果(返回结果可能是清除副作用的函数或者undefined)
    }
    _index++
}

(3)[state,dispatch] = useReducer(reducer,initArgs[,initFn])

(ps<useReducer三个优势>:在某些场景下,useReducer 会比 useState 更适用,例如

? ① state 逻辑较复杂且包含多个子值

? ② 下一个 state 依赖于之前的 state 等。

? ③ 并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

var memorizedState
const useReducer = (reducer,initArgs,initFn)=>{
    var initState
    if(typeof initFn === 'function'){
        initState = initFn(initArgs)
    }else{
        initState = initArgs
    }
    
    function dispatch(action){
    	memorizedState = reducer(memorizedState,action)
        render()
    }
    memorizedState = memorizedState || initState
    return [memorizedState,dispatch]
}

用useReducer实现useState(我只能写出在一个组件中只能用一次的useState)

function useState(initState){
	return useReducer((_,newState)=>newState,initState)
}

函数式组件与类组件的区别,优缺点

(1)性能上差异不大

(2)区别:

? 编写形式、状态管理、生命周期、调用方式、获取渲染的值、复用

? ① 状态管理和生命周期:类组件有自己的状态,函数组件也可以通过useState维护状态。类组件的生命周期是继承自React.Component的,因此函数组件没有生命周期,但是可以通过useEffect模拟。类组件和函数组件状态管理的方式不同,但是可以实现的功能大致相同。

? ② 调用方式:React内部调用方式,函数组件是直接执行,类组件是先实例化,再调用实例的生命周期方法。

? ③ 获取渲染的值:在react中,props和state都是不可变的,但是this永远是可变的,这会导致类组件可能渲染一个“过于新”的this.state或者this.props,比如在组件中注册了异步请求的回调函数,但是在回调执行前组件重新渲染了,那么回调函数是从当前组件的this上取得props和state(解决方法是箭头函数和bind)。但是函数组件的props不是从this上读取的,因为它根本没有this,它的props是保存在当前作用域的闭包中的。

? ④ 为了解决组件逻辑复用的问题,类组件应用高阶组件和render props的设计模式,相比之下函数组件逻辑复用更加清晰和优雅,可以用自定义的useXXX钩子来抽离和封装事务逻辑(比如可以写一个请求最新新闻的钩子)。

https://blog.csdn.net/yiyueqinghui/article/details/121278395(组件/逻辑复用的区别,有例子)

组件逻辑复用 HOC render props useDIY

用HOC、renderProps、useHooks复用鼠标移动逻辑:

//HOC   export default withMouseMove(OriComponent)
/*
HOC的缺点
(1)难以溯源。如果原始组件A通过好几个HOC的构造,最终生成了组件B,这个就不知道哪个属性来自于哪个HOC,需要翻看每个HOC才知道各自做了什么事情,使用了什么属性
(2)props属性名的冲突。某个属性可能被多个HOC重复使用。
(3)静态构建。新的组件是在页面构建之前生成,先有组件,后生成页面
*/
import React, { Component } from 'react'

function withMouseMove(Comp){
    return class extends Component{
        state = {
            x:0,y:0
        }
        handleMouseMove = (event)=>{
            this.setState({
                x:event.clientX,
                y:event.clientY
            })
        }
        render(){
            const {x,y} = this.state
            return (
                <div style={{height:'100%',width:'100%'}} onMouseMove={this.handleMouseMove}>
                    <Comp 
                        {...this.props}
                        mouse={{x,y}}
                    />
                </div>
            )
        }
    }
}

function App({mouse:{x,y}}){
    return (
        <div style={{height:'100vh'}}>
            {x},{y}
        </div>
    )
}

export default withMouseMove(App)
//render props 把OriComponent放在MouseMove组件的this.props.render函数中返回,ori使用Mouse传入的state作为自己的props //进行渲染
import React, { Component } from 'react'

class Mouse extends Component{
    state = {x:0,y:0}
    handleMouseMove = (event)=>{
        this.setState({
            x:event.clientX,
            y:event.clientY
        })
    }
    render(){
        const {x,y} = this.state
        return (
            <div style={{height:'100vh',width:'100%'}} onMouseMove={this.handleMouseMove}>
                {this.props.render({x,y})}
            </div>
        )
    }
}

export default function App(){
    return (
        <Mouse render={({x,y})=>{
                return (<>({x},{y})</>)
            }} 
        />
    )
}
//usehooks 抽离和封装handleMouseMove逻辑,组件直接使用封装好的handleMouseMove和state
import {useState} from 'react'
function useMouse(){
    const [{x,y},setPosition] = useState({x:0,y:0})
    const handleMouseMove = (event)=>{
        setPosition({
            x: event.clientX,
            y: event.clientY
        })
    }
    return [{x,y},handleMouseMove]
}

export default function App(){
    const [{x,y},handleMouseMove] = useMouse()
    return (
        <div style={{height:'100vh',width:'100%'}} onMouseMove={handleMouseMove}>
            ({x},{y})
        </div>
    )
}

错误控制

(0)错误边界、try…catch、window.onerror

(1)类组件:

static getDerivedStateFromError(error)

componentDidCatch(error,info)

? error:后代组件引发的错误,info:存储哪个组件引发了此错误的componentStack跟踪

(2)通用:

try…catch

window.onerror

(3)示例:

  1. 模拟一个Error组件

    import {Component} from 'react'
    
    export default class TestError extends Component{
        componentDidMount(){
            setTimeout(()=>{
                throw new Error('something went wrong')
            },2000)
        }
    
        render(){
            return (
                <div>error will happen soon</div>
            )
        }
    }
    
  2. HOC

    import React, { Component } from 'react'
    
    export default function withErrorBoundary(Com){
        return class extends Component{
            state = {
                hasError:false
            }
    
            static getDerivedStateFromError(error){
                return {hasError:error}
            }
    
            componentDidCatch(error,errorInfo){
                console.log('发送错误到服务器',error,errorInfo)
            }
    
            render(){
                return this.state.hasError ? 
                <div>Error occured</div> :
                <>
                    <Com {...this.props}/>
                </>
            }
        }
    }
    
    //使用示例
    const TestErrorWithErrorBoundary = withErrorBoundary(TestError)
    
  3. RenderProps

    import React, { Component } from 'react'
    
    export default class ErrorRenderProps extends Component {
        state = {
            hasError:false
        }
    
        static getDerivedStateFromError(error){
            return {hasError:error}
        }
    
        componentDidCatch(error,errorInfo){
            console.log('发送错误到服务器',error,errorInfo)
        }
    
        render() {
            return this.state.hasError ? 
            <div>ErrorRenderProps</div> : 
            this.props.render()
        }
    }
    
    //使用示例
    <ErrorRenderProps render={()=><TestError/>}/>
    
  4. try…catch

    import { useState,useEffect } from "react";
    export default function ErrorTryCatch(){
        const [hasError,setHasError] = useState(false)
        useEffect(()=>{
            setTimeout(()=>{
                setHasError(true)
            },2000)
        },[])
        try{
            if(hasError){
               throw new Error('error occurs') 
            }
        }catch(error){
            console.log(error,'sending to server...')
            return (<div>something went wrong</div>)
        }
        return (<div>Error will occur soon</div>)
    }
    
    //使用示例 直接用
    
  5. window.onerror

    //index.js 即在全局环境下设置window.onerror函数用于捕获全局的错误
    window.onerror = function(error){
      console.log('sending to server...',error)
      ReactDOM.unmountComponentAtNode(document.getElementById('root'))
      ReactDOM.render(
        <div>something went wrong</div>,
        document.getElementById('root')
      )
    }
    
    ReactDOM.render(
      <TestError/>,
      document.getElementById('root')
    );
    

路由及路由传参

  1. params传参

    //路由配置: 
    <Route path='/Demo/:id' component={Demo} />
    //导航:
    <NavLink to={'/Demo/123456'} />
    /*或*/this.props.history.push('/Demo/123456')
    //取值: 
    this.props.match.params.id
    
    /*
    优势:刷新,参数依然存在
    缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
    */
    
  2. query传参

    //路由配置: 
    <Route path='/Demo' component={Demo} />
    //导航:
    <Link to={{pathname:'/Demo',query:{id:123456}}} />
    /*或*/ this.props.history.push({pathname:'/Demo',query:{id:123456}})
    //取值:
    this.props.location.query.id
    
    /*
    优势:传参优雅,传递参数可传对象;
    缺点:刷新地址栏,参数丢失(不管是hash方式,还是Browser模式都会丢失参数)
    */
    
  3. state传参

    //同query差不多,只是属性不一样,而且state传的参数不在url显示,query传的参数是公开的
    //路由配置:
    <Router path='/Demo' component={Demo} />
    //导航:
    <Link to={{pathname:'/Demo',state:{id:123456}}} />
    /*或*/this.props.history.push({pathname:'/Demo',state:{id:123456}})
    //取值:
    this.props.location.state.id
    /*
    优势:传参优雅,传递参数可传对象
    缺点:刷新地址栏,hash方式会丢失参数,Browser模式不会丢失参数
    */
    
  4. search传参

    //路由配置:
    <Router path='/Demo' component={Demo} />
    //导航:
    <Link to='/Demo?id=123456&name=zy' />
    /*或*/this.props.history.push({pathname:'/Demo',search:'?id=123456&name=zy')
    //取值:
    this.props.location.search...//从字符串上取出值 注意:search字符串的第一个字符为?
    

immutable是什么,原理,使用场景,优缺点

  1. 原理

是react 不可变数据结构的实现。它实现了完全的持久化数据结构,使用结构共享,所有的更新会返回新的值,但是内部结构是共享的,意义在于减少内存占用,以及在useMemo、useEffect等包含依赖项的钩子中,能够避免传递无效依赖,比如dep参数是一个动态生成的对象,尽管值一样,但是由于比较的是引用地址,所以依赖无效。

immutable data的更新机制:

(1)immutable data是一旦创建就不能更改的数据,对immutable对象的任何更新都会返回一个新的immutable对象

(2)immutable实现原理是持久化数据结构,也就是使用旧数据创建新数据时要保证旧数据不变且可用

(3)为保证深拷贝把所有节点复制一遍带来的性能问题,Immutable data使用共享数据结构,即如果对象树中一个节点变化,新生成的immutable对象树只修改该节点以及受它影响的父节点(逐层往上直到根节点) ,而其它节点与旧数据共享。

  1. 优点

(1)降低了mutable(可变)带来的复杂度,因为mutable意味着time和value是耦合的,尤其是在js这种弱类型语言中,这(mutable)可能会导致变量在过程中发生不可预计的变化,比如

function touchAndLog(touchFn) {  
    let data = { key: 'value' };
    touchFn(data);
    console.log(data.key); //猜猜会打印什么?
}
//如果data是mutable,无法预计
//如果data是immutable,则必定输出'value'

(2)节省内存:immutable data是结构共享的,能够尽量复用内存

(3)由于每次数据一旦产生就不会再变化,因此可以保存在一个数组中,利于撤销/重做等功能的开发

(4)并发安全:由于数据天生不可变,因此不需要并发锁来保证数据在并发过程中不被更改

(5)利于函数式编程,因为纯函数就是只要输入一致,输出必然一致

  1. 缺点

新的api,增加了资源大小,容易与原生对象混淆

  1. 其他

Object.freeze和const为什么不能做不可变?因为它们只是浅比较

seamless-immutable.js 库的大小相对小很多

服务端渲染

React各种设计的具体使用场景

  1. 可中断的异步更新

  2. 高优先级任务

  3. shouldComponentUpdate

  4. getSnapshot

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:09:15  更:2022-05-05 11:13:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:14:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码