响应式系统与React

React是JavaScript库,是MVC中的view层,与用户的界面打交道。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

React设计思路

UI编程痛点:

  • 状态(即声明的变量)更新,UI不会自动更新,需要手动调用DOM进行更新;
  • 代码没有组件化,欠缺代码封装与隔离;
  • UI之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到回调地狱。

响应式编程:

  • 状态更新,UI自动更新
  • 前端代码组件化,可复用,可封装
  • 状态之间的互相依赖关系,只需声明即可(如A=B+C的关系,每次不需要再手动维护)

React的设计与实现——组件化:

  • 组件是原子组件或组件的组合
  • 组件内拥有状态,外部不可见
  • 父组件可将状态传入子组件内部

React是单向数据流(父组件给子组件传东西)
组件设计:

  1. 组件声明了状态和UI的映射(根据当前的State和Props返回一个UI)
  2. 组件有Props和State 两种状态(Props是父组件传给它的状态/State是组件自身的私有状态)
  3. 组件可由其它组件拼装而成

React 推荐使用组合来进行组件的复用,而不是继承,背后有什么样的考虑
降低代码耦合度,提高复用性。通过继承实现的代码复用是一种“白盒复用”,通过组合实现代码复用是一种“黑盒复用”,对象内部细节不可见。

React基础

Props/State/Refs
React属性Props:组件其实就是函数,接受特定的输入(props),产生特定的输出(React Elements.return jsx)。React的组件必须像纯函数那样使用其props,props是外部传入的,不可修改。
React状态State:状态是私有的,组件内部的数据可以动态改变,通过this.setState()方法。
Refs:Refs可以用来操作DOM,使用场景有:获取图片宽高、输入框聚焦。要写成回调形式的ref。
Forms表单是受控组件,用Refs将其转换为非受控组件。
Context
Props是由上到下单向传递的,Context提供了在组件中共享全局数据的方法。
React.createContext() Context是一个对象,里面有两个变量:Provider和Consumer,都是组件,在根节点外侧用Provider进行包裹,在需要使用此数据的结点外侧用Consumer包裹。
组件间通信
利用redux可以支持任何形式的通信。
父向子:
父组件向子组件传递props,子组件通过props拿到数据。

function Child(props){//子
    console.log(props.n)
    return <h1>子组件</h1>
}
function Father(props){//父
    return <Child n={props.num}/>
}
function App() {//祖先
	const [num,setNum]=useState(1);
    return <Father num={num}/>
}

子向父:
利用回调函数:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信;(真正干活的永远是父组件)

function Child(props){
    console.log(props.n)
    return (
        <>
        <h1>{props.n}</h1>
        <button onClick={()=>props.changeNum(123)}>点击修改num</button>
        </>
    )
}
function Father(props){
    return <Child n={props.num} changeNum={props.changeNum}/>
}
function App() {
	const [num,setNum]=useState(1);
    const changeNum=(arg)=>{
        setNum(arg)
    }
    return <Father num={num} changeNum={changeNum}/>
}

利用ref,父组件拿到子组件的ref也就是组件的实例,通常类组件的方法绑定到实例上,函数组件可以通过useImperativeHandle来拿到方法。(通过useImperativeHandle就可以限制要将子组件里的哪些属性或方法暴露给父组件)
祖孙跨级通信:

  • 利用中间组件层层传递props(不建议);
  • 使用context对象
import {useState,createContext} from 'react'
const numContext=createContext();//创建上下文空间
function Child(){
   return (
       <numContext.Consumer>
           {
               ({num,setNum})=>{
                   return (
                       <>
                       <h1>{num}</h1>
                       <button onClick={()=>{setNum(456)}}>点击修改num</button>
                       </>
                   )
               }
           }
       </numContext.Consumer>
   )
}
function Father(){
   return <Child />
}
function App() {
   const [num,setNum]=useState(1);
   return (<numContext.Provider value={{num,setNum}}>
           <Father />
   </numContext.Provider>)
}
  • 使用useContext这个hook(因为consumer那里代码太多了,因此用useContext会更简洁)
import {useState,useContext,createContext} from 'react'
const numContext=createContext();//创建上下文空间
function Child(){
    const {num,setNum}=useContext(numContext);
    return (
            <>
            <h1>{num}</h1>
            <button onClick={()=>{setNum(456)}}>点击修改num</button>
            </>       
    )
}
function Father(){
    return <Child />
}
function App() {
	const [num,setNum]=useState(1);
    return (<numContext.Provider value={{num,setNum}}>
            <Father />
    </numContext.Provider>)
}

非嵌套组件通信:
利用二者共同父组件的 context 对象进行通信,通过一个共同的祖先来保存数据,然后分别传递给这两个组件。
生命周期钩子函数(生命周期回调函数)
对比新旧:废弃了三个will钩子(其中componentWillReceiveProps在组件第二次接收参数时调用),提出两个新的钩子
常用的三个钩子:componentDidMount componentWillUnmount render
在这里插入图片描述
新:
在这里插入图片描述
React的render函数,哪些情况下会被重新执行
当组件的State/传入的props发生改变、或forceupdate()时,组件和其子组件会递归地重新调用render函数。

React类式组件

在类式组件中,普通函数中使用this是undefined。因为普通函数中this是调用的时候确定的。
解决:使用箭头函数去定义函数(推荐);或者如下在constructor中通过bind绑定this为App;或者在onClick里面使用箭头函数;也可以在onClick中去bind this。

class App extends Component<any,any>{
  constructor(props:number){
    super(props);
    this.state={count:0};
    //this.handle=this.handle.bind(this);
  }
  handleArrow=()=>{
    console.log('箭头函数this',this);//App
    this.setState({count:this.state.count+1})
  }
  handle(){
    debugger;
    console.log('普通函数this',this);//undefined
  }
  render(){
    return (
      <div>
      <button onClick={this.handleArrow}>箭头函数{this.state.count}</button>
      <button onClick={this.handle}>普通函数</button>
      <button onClick={()=>{this.handle()}}></button>
      <button onClick={this.handle.bind(this,这里还可以传参)}></button>
      </div>
    )
}}

setState怎么用,传什么进去?setState是一个调和的过程(reconciler),第一个参数可以传入对象,也可以是函数,第二个为可选参数,参数为一个回调函数。如果state的更新不依赖于之前的state中的值,使用对象;如果state的更新依赖之前state中的值,则第一个参数传入函数。
异步setState多次调用如何处理
setState({}): 合并更新一次状态, 只调用一次render()更新界面 —状态更新和界面更新都合并了
setState(fn): 更新多次状态, 但只调用一次render()更新界面 —状态更新没有合并, 但界面更新合并了
this.setState是同步还是异步知根知底setState
对于不同模式的react,这道题的答案是不一样的:
开启legacy模式时:即老版本通过reactDOM.render创建的应用,当命中batchedUpdates(多个setState合并为一个)时,就是异步的,当没有命中batchedUpdates时,就是同步的;
开启concurrent模式时:即新版本的reactDOM.createRoot时,则都是异步的。

class App extends Component{
    state={num:0}
    addNum=()=>{
        this.setState({num:this.state.num+1})
        console.log(this.state.num)//打印0
    }
    render(){
        return (
            <>
                <h3>数字为:{this.state.num}</h3>
                <button onClick={this.addNum.bind(this)}>按钮</button>
            </>
        )
    }
}
export default App;
//解决:this.setState第二个参数是一个回调,可选。回调执行的时机是setState完成之后。把要做的事情放到setState的第二个参数(回调)里,等状态改变以后做某些事情。
    addNum=()=>{
        this.setState({num:this.state.num+1},()=>{
            console.log(this.state.num)//打印1
        })
    }

在函数式组件中如何解决:

    const addNum=()=>{
        setNum(num+1);
    }
    useEffect(()=>{
        console.log(num)
    },[num])

类式组件中的constructor含义,执行constructor与不执行的区别,constructor中为什么一定要使用super
其实这个不算react的知识点,而是ES6类的知识点。super作为函数调用,代表调用父类的构造函数。如果不接收参数props,可以不写constructor,但会自动添加一个constructor。

React hooks

hooks介绍和原理
因为函数式组件没有生命周期,没有this,没有state。所以出现了hooks。hooks是指可以挂到React生命周期上去执行的函数。
官方hooks
useState:传入一个变量的初始值,返回一个数组[状态(变量),set该状态的函数],用户可以通过调用该函数来实现状态的修改;
函数组件中的setState中没有提供第二个参数(回调),但也想state值发生改变之后去做某件事情,该怎么办? 可以用useEffect,依赖项就设为这个state。(见上面代码)
useReducer:useState的替代方案,管理复杂的状态。
const [state,dispatch]=useReducer(reducer,initialState);
接收一个形如 (state, action) => newState 的reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
以下是用 reducer 重写 useState 一节的计数器示例:

function App() {
  const reducer=(state:any,action:any)=>{
    switch(action.type){
      case 'add':
        return {count:state.count+1};
      case 'minus':
        return {count:state.count-1}
      default: 
        throw new Error(); 
    }
  }
  const initialState={count:0};
  const [state,dispatch]=useReducer(reducer,initialState);
   return (
     <> 
        <h3>{state.count}</h3>
        <button onClick={()=>{dispatch({type:'add'})}}>+</button>
        <button onClick={()=>{dispatch({type:'minus'})}}>-</button>
     </>
   );
}

useEffect:接收两个参数,一个回调函数,一个依赖数组(不写的话就默认状态更新也会渲染)。没有依赖项时,传入的函数在组件第一次挂载时和状态更新时会执行。(有点类似于render())
有“副作用”的函数,需要传入useEffect来执行。副作用意思是除了单纯计算状态以外,还要做一些其他事情比如发起网络请求、更新DOM、给window绑定监听事件、localStorage存储数据等。
在React中分为无需清除的Effect和需要清除的Effect。
不需要清除的Effect:

 useEffect(()=>{
     document.title=`点赞了${like}`//useEffect在组件渲染和状态更新时会自动执行
 })

需要清除的Effect:比如绑定的事件要取消监听。
在类式组件中如何实现(在componentWillUnmount时清除(回收数据、清理垃圾等操作)):
在这里插入图片描述
useEffect模拟了三个生命周期:DidMount\DidUpdate\WillUnmount。可以避免componentDidMount和componentWillUnmount的重复。直接在回调里return一个回调函数。
useEffect原理:添加链接描述
useContext:见上面跨级组件传参那里
useRef:useRef 会在每次渲染时返回同一个对象,在DOM节点上定义ref属性,通过.current就可以获取到该DOM元素
useRef详细总结
插播一个受控组件非受控组件
受控组件和非受控组件只存在于表单元素,受控组件就是表单元素的value需要通过state来定义,
不受控组件意味着表单元素的value不能通过state来获取,只能使用ref来获取。
受控组件(更好):

function App() {
	const [val,setVal]=useState(1);
    const inputChange=(e)=>{
        //console.log(e.target)
        setVal(e.target.value);
    }
    return (
        <>
            <input type='text' value={val} onChange={inputChange}></input>
        </>
    )
}

不受控组件:

function App() {
    const element=useRef(null)
    return (
        <>
            <input type='text' ref={element}></input>
            <button onClick={()=>{console.log(element.current.value)}}>点击</button>
        </>
    )
}

memo、useMemo、useCallback
有时父组件更新会导致子组件进行没必要的更新,属于性能优化的点。我们期待的结果:子组件的 props 和 state 没有变化时,即便父组件渲染,也不要渲染子组件。
memo这个hook可以缓存组件使其不受父级组件更新的影响(仅限于纯静态的情况),带有了shouldComponentUpdate()这个特性:

import {memo, useState} from 'react'
const Child=memo(()=>{
    console.log('我渲染了')
    return <div>我是子组件</div>
})
function App() {
    const [num,setNum]=useState(1);
    return (
        <>
            <h3>数字为:{num}</h3>
            <button onClick={()=>setNum(num+1)}>按钮</button>
            <Child/>
        </>
    )
}

useMemo和useCallback解决了什么问题?
区别:useMemo 返回值是一个缓存的值 ;useCallback返回值是 一个缓存的回调函数。使用场景不同:useMemo适用场景是组件更新时,一些计算量很大的值也有可能被重新计算,这个时候就可以使用 useMemo 直接使用上一次缓存的值;useCallback父组件更新时,通过props传递给子组件的函数也会重新创建,然后这个时候使用 useCallBack 就可以缓存函数不使它重新创建。

自定义hook
将组件逻辑提取到可重用的函数中;不同组件引用自定义hook时,state是互相隔离的。
高阶组件(Higher Order Component)HOC的缺点:
高阶组件就是一个函数,该函数接收一个组件作为参数,返回一个新组件。
返回了一堆无关的标签结构,而自定义hook只需要拿到hook中的变量即可;代码冗余不方便阅读。
推荐使用自定义hook代替HOC。
hooks使用法则
只在最顶层使用hooks,不要在循环、条件或嵌套函数中使用hooks;(为什么只能在react函数的最顶层使用hooks? hooks是以单链表的形式存储在fiber.memorizedState里的(类组件,fiber.memorizedState里存的是实例,函数组件存的是hooks),hooks没有名字,为了区分这些hook,必须保证这个链表结点顺序的稳定性,方便新旧结点对比)
为什么只在React组件中使用hooks而不要在普通函数中使用?因为状态值需要存起来,才能在上一次的基础上做变动,状态值是存在虚拟dom上的,只有react组件中才有fiber结点

函数式组件与类式组件相比,优势在哪里
1.函数式编程,组件尽量写成纯函数;类式组件是基于继承的,与父类组件耦合了。
2,类式组件依赖于生命周期,比如请求两份不同的数据,却都要写在componentDidMount里;hooks颗粒度更小,可以将state、生命周期钩子根据业务进行分离;且hooks可以在无需修改组件结构的情况下复用状态逻辑,提高了组件的复用性。
3,class继承React.Component,hooks只有一个React.createElement。
hooks缺点
1,闭包陷阱。比如在useEffect中用定时器,空数组没有依赖项,count改变,但是组件只有第一次挂载的时候执行了useEffect,setTimeout一直用的是fn执行完毕后留下的闭包count。解决:添加依赖项count,或者用useRef:组件每一次渲染时 useRef 返回的都是同一个对象引用(即引用不变),那么每次通过这个引用去取其中的属性中的值的时候,自然就能够拿到最新的数据了。
添加链接描述
2,类式组件可以做的hooks做不到的场景:
①hooks没有一种完全替换shouldComponentUpdate()的机制。(purecomponent是类式组件的,react.memo有了purecomponent的功能)
②类式组件基于生命周期。
③类组件有实例,如果需要用到实例的话,类组件是首选。
总结
hooks封装性更好,更易复用。具体使用还是得看场景。
函数组件中如何实现forceUpdate?
类式组件中提供了API,直接调用this.forceUpdate()。
函数组件中可以通过自己自定义一个hook,出参是update这个函数:
在这里插入图片描述

React状态管理库

将状态抽离到UI外部,放到一个store里统一管理。缺点:降低复用性,因为组件与组件外部的一个store强耦合了。
redux自述
在这里插入图片描述
用户在 view 层通过 dispatch 来派发 action 给store,当 store 接收到用户传递过来的 action 后,会把旧的state 和action 传给 reducer ,而 reducer 会根据 action 的 type,来返回一个新的 state。而当 store 有变化的时候,store 就会调用监听函数 store.subscribe,从而使得 view 层发生变化。
react-redux介绍和原理
集中式的状态管理库npm install redux react-redux
有点像context。context是用consumer来获得别人的状态,这个是用connect来获得。都是provider包裹顶级组件。
在这里插入图片描述
步骤:
1,src下创建store文件夹,包含两个文件:index.js和reducer.js。
2,在store文件夹下面的index.js创建store,Redux 提供了一个createStore来生成Store。其中createStore接收一个reducers。
在reducer.js中接收旧的state和action,返回新的state,这个计算过程就叫reducer,reducer是一个函数。

const defaultState={
    num:1,
}
//导出一个函数,返回新的state
export default (state=defaultState,action)=>{
    let newState=JSON.parse(JSON.stringify(state))
    switch(action.type){
     case 'addNum':
         state.num+=action.value;//这可以看出来真正干活的还是reducer.js
         break;
 }
    return state;
}

3,在总的index.js文件中,用Provider包裹App组件(顶级组件),并注明store。
import {Provider} from 'react-redux'

<Provider store={store}><App/> <Provider/>

4,在App.js中引入connect,import {connect} from 'react-redux'。connect接受两个参数:mapStateToProps,mapDispatchToProps。

//App.js
import {connect, createDispatchHook} from 'react-redux'
function App(props) {
    return( <>
        <h3>数字为:{props.num}</h3>
        <button onclick={props.leijia}>累加按钮</button>
    </>)
}
//状态映射:将reducer中的state映射成props,让开发者可以在组件中使用props.num去调用state中的num
const mapStateToProps=(state)=>{
    return {
        num:state.num
    }
}
//事件派发映射:将reducer中的事件映射成props,让开发者可以在组件中(这里是App这个组件)使用props.add()去实现state中num的累加
const mapDispatchToProps=(dispatch)=>{
    return {
        leijia(){
            const action={type:'addNum',value:2}//action本质上是一个js对象,代表要变化的数据,action内必须使用一个字符串类型的 type 字段来表示将要执行的动作。除了 type 字段外,action 对象的结构完全由你自己决定。{ type:'GET_ALL', text:data }。 data 就是我们要存入store的数据 这样就是把数据存在仓库里面了,这个项目中的任意组件就都可以直接得到他的数据了。(通过props.text)
            dispatch(action)//action是数据从应用传递到 store 中的有效载荷。它是 store 数据的唯一来源!一般来说你会通过 store.dispatch() 将 action 传到 store。action和store之间沟通的桥梁是dispatch。
       }
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(App);

redux中的reducer为什么必须是纯函数
纯函数:给定固定的输入,就一定有固定的输出,而且不会有任何副作用。
reducer作用:接收旧的state和action,返回新的state。reducer的职责不允许有副作用,副作用即不确定性,如果reducer有副作用,那么返回的state就不确定了。因为比较两个state对象中所有的属性是否完全相同,唯一的办法就是深比较,然而,深比较在真实的应用中代价是非常大的,非常耗性能,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回旧的对象。
redux中间件原理
在这里插入图片描述
在这里插入图片描述
Redux 的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,可以进行一些额外的操作,会被 Store 上的多个中间件依次处理(类似于webpack中的loader)。例如可以利用中间件来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

简单来说redux中间件原理:其实就是store.dispatch()的增强,对其功能做了扩展。(action和store之间沟通的桥梁是dispatch)
redux异步管理中间件:redux-thunk、redux-saga、redux-promise:
之前action只能是一个js对象,所以action是一个对象通过dispatch直接派发给了store。但是现在,当使用了redux-thunk之后,action可以是函数了。
redux-thunk 中间件的功能很简单:首先检查参数 action 的类型,如果是对象,和之前一样,且调用 next 让下一个中间件继续处理 action 。如果是函数的话,就执行这个 action 函数,在函数内部我们想要真正dispatch一个action对象的时候再执行dispatch即可。

redux-thunk是把异步操作放在action里面操作。而redux-saga采用的设计思想是,单独的把一个异步逻辑拆分出来,放在一个异步文件里面管理。另外还有一些中间件如redux-logger(打印日志)、redux-promise(处理异步操作,但是返回Promise对象)。
总的来讲Redux Saga适用于对事件操作有细粒度需求的场景,同时它也提供了更好的可测试性,与可维护性,比较适合对异步处理要求高的大型项目,而小而简单的项目完全可以使用redux-thunk就足以满足自身需求了。毕竟react-thunk对于一个项目本身而言,毫无侵入,使用极其简单,只需引入这个中间件就行了。而react-saga则要求较高,难度较大。

apply-middleware的源码:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
      )
    }
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 将不同的 middlewares 一层一层包裹到原生的 dispatch 之上
    // compose 将 chain 中的所有函数组装成一个新的函数,即新的 dispatch
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}

redux中间件还有webpack中的loader都运用了compose的思想:js_手写compose

React路由

npm install react-router-dom@6

步骤:
1,src下创建pages文件夹(包含几个页面比如List.jsx、Detail.jsx、Home.jsx)
react-router-dom中有两种模式:BrowserRouter(History模式不带#号)HashRouter(Hash模式带#号)。src下创建router文件夹,在下面创建index.tsx,可以用来注册路由。
2,Link是实现路由的跳转 Link to 会被编译成 a href
//Link和NavLink(有高亮)是用来跳转路由的,需要人为去点(a标签),Route是注册路由的,history是不用点就能跳转
下图左:注册路由;右:页面跳转
在这里插入图片描述
react-router-dom里也给我们封装了几个hook:
useLocation:const location=useLocation();console.log(location.pathname) 可以获取到当前的路由
useNavigate:const navigate=useNavigate();navigate('./detail')可以实现路由跳转,定义一个回调,在button点击时调用,可以实现除了Link以外的路由跳转,还支持携带参数。
useParams:是对应跳转路由斜杠后面的参数
useSearchParams:是对应跳转路由后面带问号的参数
在这里插入图片描述
在这里插入图片描述

React的实现

在这里插入图片描述
真实DOM不是js对象,而是浏览器内部维护的对象。虚拟DOM是js内存中维护的对象。
什么是虚拟DOM?虚拟DOM解决了哪些问题?虚拟DOM运行机制?虚拟DOM
虚拟dom解决了什么问题

  1. 频繁更新dom引起的性能问题
  2. 将真实DOM和js操作解耦,减少js操作dom复杂性

页面中结点变动比较多Vdom反而性能还没有直接操作dom性能好,如果变动少,diffing能提升性能。但是仍然是用虚拟dom比较好,理由有:跨平台(虚拟dom在h5中转换成DOM树,在移动端转化成对应的native dom);与js操作解耦,直接操作dom很麻烦。
Diffing算法
每当状态改变时,返回的jsx(虚拟DOM)就会发生改变,发生改变的jsx与旧的jsx做一个diff,只更新difference的地方。
在这里插入图片描述
diff的过程是一个递归的过程,算法复杂度为O(n)

diff的流程
diff算法发生在两个阶段,分别是beginWork和completeWork阶段。
在beginWork阶段,会比对更新前后的元素类型:
如果是不同类型的元素(不同的自定义组件)就删除更新前的元素及其子元素,然后基于新的元素重新构建;
如果相同,会复用DOM元素,仅替换props;
当父元素类型相同且有多个子节点时,会有使用key的场景,React会基于key尽可能的复用oldFiber并保证较好的性能。
在completeWork阶段,React会将新老节点的props进行一次diff,通过遍历比对,找出其中的变化添加到更新队列中,在commit阶段完成更新渲染。一次性更新到真实dom中。
添加链接描述
React中key的作用
标记同级节点上当前元素的唯一性。然后对他进行相应的增删改查,减少没必要的diff,加快渲染速度。
因为对于一个组件或者节点来说,只要父节点状态或者属性发生变化,该组件就会进行diff对比,即使该组件没变化,而如果为组件引入了key值,就可以在diff对比前先做一个校验,判断该组件是否需要diff对比,即使是diff对比,也可以判断该组件是直接更新操作还是销毁或者新建操作,从而提高了diff算法的效率。
原理
因为在reactelement中有一个属性是key,该属性默认是为空值,所以一般情况下,只要组件不加上key值,react是不会去校验组件的key,而是直接采用diff算法进行对比,一旦组件加上了key值,react就会在渲染时对该组件的身份进行校验,首先校验新旧组件的key值是不是一致,不一致的话,该组件直接销毁,然后在新建该组件;如果一致,则比较组件的属性是否发生变化,如果发生变化,则采用diff算法进行对比,然后得出差异对象,如果属性没发生变化,则认为该组件不需要改变;
React遍历为什么用map不用forEach?
简单来说就是map有返回值:

    return(
        <ul>
            {arr.map((item,index)=>{
                return (<li key={index}>{item}</li>)
            })}
        </ul>
    )

Map遍历不会影响原数组,会返回一个新数组,map遍历数组会生成一个副本,使用map的return 每个值都返回给了数组的副本,否则的话每遍历一次都直接影响原数组, DOM都要重新渲染计算一次,损耗性能 。forEach的话会修改原来的数组,返回undefined。

React Fiber介绍及原理

为了解决什么问题?解决的是使得一些更高优先级的任务能够优先执行,提高用户的体验,从用户的角度不会感觉到卡顿。
React框架内部的运作可以分为三层:reconciler层(负责diff运算、调用生命周期函数等)、renderer层(根据不同平台渲染出不同的页面,比较常见的是ReactDOM和ReactNative)、虚拟dom层。
React16相比于15改动最大的就是reconciler层。
Stack Reconciler(也即react15 的Reconciler)使用递归对比VirtualDom树,需要将变动的节点找出并且更新,运作过程不能被打断;
Fiber Reconciler(也即react16 的Reconciler)每执行一段时间都会将控制权交还给浏览器。
fiber reconciler的执行过程:我所理解的react fiber架构 react fiber
React除了Fiber工作单元的拆分,还有阶段拆分:在Stack Reconcilaition中是一边进行diff,一边进行commit。而Fiber Reconcilication是分为两个阶段:1,render/reconcilation,可打断;2,commit,不可打断。
什么是Fiber
react fiber
第一种理解:工作单元。fiber由react16引入,是react新的调度算法,因为react15基于stack reconciliation,在reconcilation期间,会阻塞整个线程,一直霸占浏览器资源,导致用户触发的事件得不到响应,并且会导致掉帧,造成明显的卡顿。Fiber也称为协程,是一种控制流程的让出机制,每执行一段时间都会将控制权交还给浏览器。等浏览器忙完之后,再继续之前未完成的任务。
第二种理解:数据结构。Fiber采用链表结构,每一个VirtualDom节点,对应一个fiber节点。一个节点对应一个fiber,多个fiber节点之间建立连接,然后会生成一个fiber tree。fiber tree就是单链表树,而构建这个tree的关键属性就是return,child, sibling。
Return: 每一个fiber都有值,指向父节点
child: 指向当前节点的第一个子节点
sibling: 指向当前节点的第一个兄弟节点
stateNode: 每个fiber对象对应的组件或者dom
有了链表结构,我们就能够来处理React树的节点啦。结合浏览器调度部分和workLoop,其实FiberNode就是我们的工作单元。performUnitOfWork负责对FiberNode进行操作,进行深度遍历,返回下一个工作单元FiberNode。因为是链表结构,即使流程被中断,我们保存了上一个执行单元,能够准确地从上次未处理完成的FiberNode继续执行,恢复工作流程。
几个函数
当有更新时,会往updateQueue队列中插入任务。例如setState执行更新组件,会将任务加入队列中,等待更新的任务通过requestIdleCallback进行调用;performWork: 分段执行的任务片段。只要有空闲时间就调用执行;workLoop: 从updateQueue中获取更新任务进行执行,每执行一个执行单元,检测是否还有剩余时间,有就进入下一个执行单元,无剩余时间则保存现场,等下次拥有执行权再恢复。
在这里插入图片描述

React代码优化性能

react性能优化

  • 减少setState,与视图层无关的属性不要放到state上;
  • 不要滥用props;
  • 组件尽可能拆分和解耦,比如input和list组件,list组件最好只在自己数据变化时重新渲染,不然input更新list组件也会重新渲染;
  • 遍历时使用key;
  • 类组件中PureComponent (但因为是浅比较,所以要使用不可突变数据结构,或者用immutable.js跟踪变化):当父组件更新时,如果子组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。具体就是 React 自动帮我们做了一层浅比较,减少了自己去写shouldComponentUpdate():React性能优化:PureComponent的使用原则;函数组件中用memo、useCallback、useMemo。
  • 懒加载

React和Vue

React 这种函数式编程,和 vue 这种基于模版语法的前端框架,各有什么优势和缺点
react是单向数据流,vue是双向数据绑定,比起vue,react的单项数据流使得代码量更多一些;
react是函数式编程,易于封装和扩展,灵活度更高,具备丰富的js库;vue的灵活度不如react;
react生态圈强大,同时适用于web端和原生app
react写的是jsx,写起来更接近原生js,经过转译会变成js,自由度高;而vue用了很多模板语法,自由度变低;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值