React面试

讲概念,说用途,理思路,列优缺点

1. 什么是 React?

(历史): react是一个ui框架,jquery不是框架,只是一个工具库,随着pc性能提高,页面效果也需要提升,但是jquery没有一个模式,没发组织代码, angularjs 之后推出,提出了双向绑定的概念,是angularjs的最大特点。 View = fn(props)

(答案) react 通过组件化的方式,解决工程化和视图层的开发复用问题

声明式:直观和组合 组件化,降低系统间的耦合性,提高功能内部的聚合性 通用性: 虚拟dom不局限于web开发

react 不是一揽子框架,在技术选型上有比较高的成本

2. 为什么要用jsx

(为什么要用?这类问题) 一句话解释 核心概念 方案对比

关注点分离

jsx是js的语法扩展,react中不强制使用jsx, jsx是createElement的一个语法糖 模版不应该是开发中的关注点,违反 关注点分离 原则。所以没有引入模版语法。

4. setState 是同步更新还是异步更新

合成事件,react 给dom容器中监听事件,进行事件委托。 捕获到事件之后,模拟冒泡捕获,传递给对应的组件。 setState 不是真的异步,只是看起来像异步。 在react可以控制的事件中 ,isBatchingUpdates为ture,则为异步更新 在原生事件中,为false

异步的情况: 保持内部的一致性, 后续架构更新

5. 如何进行跨组件通信

父与子通信,通过props 回调函数

6. 为什么使用 react hook

why what how

函数式组件更契合react 的理念

类组件 this 的数据是可以被修改的,一些情况(setTimeout)下会打破了数据和渲染动作之间的关联 hook补齐了相较于类组件缺失的东西

hook 两个经典 useState,useEffect

useEffect弥补了生命周期的缺陷,能够为函数式组件引入副作用操作

why 告别难以理解的class,class 的 this 难以理解 生命周期的不合理,逻辑一度与生命周期耦合在一起。 可以通过自定义hook 达到不破坏组件结构,又能够实现逻辑复用的效果。

hooks 暂时还不能完全补齐类组件的能力 hooks在使用规则层面有严格的约束

7. hooks的原理

how

hooks 的本质是链表

mountState 首次渲染时,构建链表并渲染 updateState 按顺序调用之前构建好的链表,取出对应的数据信息进行渲染

hooks 只在函数中调用 不能再循环或者条件中调用 以上两点是为了保证 hooks 的执行顺序 为什么顺序这么重要

6. 什么是JSX?

JSX 是JavaScript XML 的简写。是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法。这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是JSX的一个例子:

render(){
    return(        
        <div>
            <h1> Hello World from Edureka!!</h1>
        </div>
    );
}
复制代码

jsx是js的语法扩展,react中不强制使用jsx, jsx是createElement的一个语法糖 模版不应该是开发中的关注点,违反 关注点分离 原则。所以没有引入模版语法。

8. 为什么浏览器无法读取JSX?

浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。

8. 为什么需要虚拟dom

虚拟dom的价值不在性能

在 React 中,虚拟 DOM 是一个轻量级的 JavaScript 对象树,它以组件树的形式描述整个用户界面。每个虚拟 DOM 节点都与实际 DOM 中的节点对应,并包含有关节点的信息。

虚拟 DOM 的作用是提供一种方式来更新和渲染界面。当组件的状态发生变化时,React 会生成一个新的虚拟 DOM 树,然后通过比较前后两个虚拟 DOM 树的差异,确定实际需要更新的部分。

通过diff 和 patch 可以最小化实际 DOM 操作的次数。React 会找出需要添加、更新或删除的 DOM 节点,然后进行一次批量更新,以最小化对实际 DOM 的访问和操作。

通过使用虚拟 DOM,React 实现了高效的 UI 更新和渲染。它只更新发生变化的部分,而不是整个界面,从而提高性能和响应性。

虚拟dom 解决的关键问题: 提高研发体验和研发效率 可以跨平台 批量更新时可以缓冲每次更新

一、React18有哪些更新?

juejin.cn/post/709403…

  1. setState自动批处理

在react17中,只有react事件会进行批处理,原生js事件、promise,setTimeout、setInterval不会

react18,将所有事件都进行批处理,即多次setState会被合并为1次执行,提高了性能,在数据层,将多个状态更新合并成一次处理(在视图层,将多次渲染合并成一次渲染)

  1. 引入了新的root API,支持new concurrent renderer(并发模式的渲染)
//React 17 import React from "react" import ReactDOM from "react-dom" import App from "./App" const root = document.getElementById("root") ReactDOM.render(<App/>,root) // 卸载组件 ReactDOM.unmountComponentAtNode(root)   // React 18 import React from "react" import ReactDOM from "react-dom/client" import App from "./App" const root = document.getElementById("root") ReactDOM.createRoot(root).render(<App/>) // 卸载组件 root.unmount()`  
  1. 去掉了对IE浏览器的支持,react18引入的新特性全部基于现代浏览器,如需支持需要退回到react17版本 4. flushSync

批量更新是一个破坏性的更新,如果想退出批量更新,可以使用flushSync

jsx

复制代码

`import React,{useState} from "react" import {flushSync} from "react-dom" const App=()=>{   const [count,setCount]=useState(0)   const [count2,setCount2]=useState(0)   return (     <div className="App">       <button onClick=(()=>{         // 第一次更新         flushSync(()=>{           setCount(count=>count+1)         })         // 第二次更新         flushSync(()=>{           setCount2(count2=>count2+1)         })       })>点击</button>       <span>count:{count}</span>       <span>count2:{count2}</span>         </div>    ) } export default App`
  1. react组件返回值更新
  • 在react17中,返回空组件只能返回null,显式返回undefined会报错
  • 在react18中,支持null和undefined返回
  1. strict mode更新

当你使用严格模式时,React会对每个组件返回两次渲染,以便你观察一些意想不到的结果,在react17中去掉了一次渲染的控制台日志,以便让日志容易阅读。react18取消了这个限制,第二次渲染会以浅灰色出现在控制台日志

  1. Suspense不再需要fallback捕获
  2. 支持useId

在服务器和客户端生成相同的唯一一个id,避免hydrating的不兼容

  1. useSyncExternalStore

用于解决外部数据撕裂问题

  1. useInsertionEffect

这个hooks只建议在css in js库中使用,这个hooks执行时机在DOM生成之后,useLayoutEffect执行之前,它的工作原理大致与useLayoutEffect相同,此时无法访问DOM节点的引用,一般用于提前注入脚本

  1. Concurrent Mode

并发模式不是一个功能,而是一个底层设计。

它可以帮助应用保持响应,根据用户的设备性能和网速进行调整,它通过渲染可中断来修复阻塞渲染机制。在concurrent模式中,React可以同时更新多个状态

区别就是使同步不可中断更新变成了异步可中断更新

useDeferredValue和startTransition用来标记一次非紧急更新

二、React的设计思想

  • 组件化 每个组件都符合开放-封闭原则,封闭是针对渲染工作流来说的,指的是组件内部的状态都由自身维护,只处理内部的渲染逻辑。开放是针对组件通信来说的,指的是不同组件可以通过props(单项数据流)进行数据交互

  • 数据驱动视图 UI=f(data) 通过上面这个公式得出,如果要渲染界面,不应该直接操作DOM,而是通过修改数据(state或prop),数据驱动视图更新

  • 虚拟DOM 由浏览器的渲染流水线可知,DOM操作是一个昂贵的操作,很耗性能,因此产生了虚拟DOM。虚拟DOM是对真实DOM的映射,React通过新旧虚拟DOM对比,得到需要更新的部分,实现数据的增量更新

React设计模式

三、JSX是什么,它和JS有什么区别

JSX是react的语法糖,它允许在html中写JS,它不能被浏览器直接识别,需要通过webpack、babel之类的编译工具转换为JS执行 JSX与JS的区别:

  1. JS可以被打包工具直接编译,不需要额外转换,jsx需要通过babel编译,它是React.createElement的语法糖,使用jsx等价于React.createElement
  2. jsx是js的语法扩展,允许在html中写JS;JS是原生写法,需要通过script标签引入

为什么在文件中没有使用react,也要在文件顶部import React from “react”

只要使用了jsx,就需要引用react,因为jsx本质就是React.createElement

注意,在React 17RC版本后,jsx不一定会被转换为React.createElement了

以下代码

function App(){     return <h1>hello,lyllovelemon</h1> }`    

react17将会通过编译器babel/typescript转换为

import {jsx as _jsx} from 'react/jsx-runtime'; function App() {   return _jsx('h1', { children: 'hello,lyllovelemon' }); }

此时就不需要通过import React就能使用jsx了(用react hooks还是需要导入React)

为什么React自定义组件首字母要大写

jsx通过babel转义时,调用了React.createElement函数,它接收三个参数,分别是type元素类型,props元素属性,children子元素。

如下图所示,从jsx到真实DOM需要经历jsx->虚拟DOM->真实DOM。如果组件首字母为小写,它会被当成字符串进行传递,在创建虚拟DOM的时候,就会把它当成一个html标签,而html没有app这个标签,就会报错。组件首字母为大写,它会当成一个变量进行传递,React知道它是个自定义组件就不会报错了

`<app>lyllovelemon</app> // 转义后 React.createElement("app",null,"lyllovelemon") <App>lyllovelemon</App> // 转义后 React.createElement(App,null,lyllovelemon)`

React组件为什么不能返回多个元素

这个问题也可以理解为React组件为什么只能有一个根元素,原因:

  1. React组件最后会编译为render函数,函数的返回值只能是1个,如果不用单独的根节点包裹,就会并列返回多个值,这在js中是不允许的
class App extends React.Component{
  render(){ 
    return(
    <div>
     <h1 className="title">lyllovelemon</h1>
      <span>内容</span>    
    </div>    
  )
}

//编译后
class App extends React.Component{
  render(){
    return React.createElement('div',null,[
      React.createElement('h1',{className:'title'},'lyllovelemon'),
      React.createElement('span'),null,'内容'
      ])
  }
}
  1. react的虚拟DOM是一个树状结构,树的根节点只能是1个,如果有多个根节点,无法确认是在哪棵树上进行更新

vue的根节点为什么只有一个也是同样的原因

React组件怎样可以返回多个组件

  • 使用HOC(高阶函数)
  • 使用React.Fragment,可以让你将元素列表加到一个分组中,而且不会创建额外的节点(类似vue的template)
renderList(){
  this.state.list.map((item,key)=>{
    return (<React.Fragment>
      <tr key={item.id}>
        <td>{item.name}</td>
        <td>{item.age}</td>
        <td>{item.address}</td>
      </tr>    
    </React.Fragment>)
  })
}
  1. 使用数组返回
renderList(){
  this.state.list.map((item,key)=>{
    return [
      <tr key={item.id}>
        <td>{item.name}</td>
        <td>{item.age}</td>
        <td>{item.address}</td>
      </tr>
    ]
  })
}

React中元素和组件的区别

react组件有类组件、函数组件

react元素是通过jsx创建的

const element = <div className="element">我是元素</div>

四、简述React的生命周期

生命周期指的是组件实例从创建到销毁的流程,函数组件没有生命周期,只有类组件才有,因为只有class组件会创建组件实例

组件的生命周期可以分为挂载、更新、卸载阶段,下面说的是React16.3版本后的生命周期

挂载

constructor 初始化阶段,可以进行state和props的初始化

static getDerivedStateFromProps 静态方法,不能获取this

render 创建虚拟DOM的阶段

componentDidMount 第一次渲染后调用,挂载到页面生成真实DOM,可以访问DOM,进行异步请求和定时器、消息订阅

更新

当组件的props或state变化会触发更新

static getDerivedStateFromProps

shouldComponentUpdate 返回一个布尔值,默认返回true,可以通过这个生命周期钩子进行性能优化,确认不需要更新组件时调用

kotlin

复制代码

shouldComponentUpdate(nextProps,nextState){ // 属性和状态都没有变化 就没必要更新渲染 if(this.props === nextProps && this.state === nextState){ return false } // 返回值为true才会执行后续的生命周期 return true }

render 更新虚拟DOM

getSnapShotBeforeUpdate 获取更新前的状态

componentDidUpdate 在组件完成更新后调用,更新挂载后生成真实DOM

卸载

componentWillUnmount 组件从DOM中被移除的时候调用,通常在这个阶段清除副作用,比如定时器、事件监听等

错误捕获

static getDerivedStateFromError 在errorBoundary中使用

componentDidCatch

render是class组件中唯一必须实现的方法

五、React事件机制

什么是合成事件

React基于浏览器的事件机制实现了一套自身的事件机制,它符合W3C规范,包括事件触发、事件冒泡、事件捕获、事件合成和事件派发等

React事件的设计动机(作用):

  • 在底层磨平不同浏览器的差异,React实现了统一的事件机制,我们不再需要处理浏览器事件机制方面的兼容问题,在上层面向开发者暴露稳定、统一的、与原生事件相同的事件接口
  • React把握了事件机制的主动权,实现了对所有事件的中心化管控
  • React引入事件池避免垃圾回收,在事件池中获取或释放事件对象,避免频繁的创建和销毁

React事件机制和原生DOM事件流有什么区别

虽然合成事件不是原生DOM事件,但它包含了原生DOM事件的引用,可以通过e.nativeEvent访问


DOM事件流是怎么工作的,一个页面往往会绑定多个事件,页面接收事件的顺序叫事件流

W3C标准事件的传播过程:

  1. 事件捕获
  2. 处于目标
  3. 事件冒泡

常用的事件处理性能优化手段:事件委托

把多个子元素同一类型的监听函数合并到父元素上,通过一个函数监听的行为叫事件委托

我们写的React事件是绑定在DOM上吗,如果不是绑定在哪里

React16的事件绑定在document上, React17以后事件绑定在container上,ReactDOM.render(app,container)

React事件机制总结如下:

事件绑定 事件触发

  • React所有的事件绑定在container上(react17以后),而不是绑定在DOM元素上(作用:减少内存开销,所有的事件处理都在container上,其他节点没有绑定事件)

  • React自身实现了一套冒泡机制,不能通过return false阻止冒泡

  • React通过SytheticEvent实现了事件合成

React实现事件绑定的过程

1.建立合成事件与原生事件的对应关系

registrationNameModule, 它建立了React事件到plugin的映射,它包含React支持的所有事件的类型,用于判断一个组件的prop是否是事件类型

css

复制代码

{ onBlur:SimpleEventPlugin, onClick:SimpleEventPlugin, onClickCapture:SimpleEventPlugin, onChange:ChangeEventPlugin, onChangeCapture:ChangeEventPlugin, onMouseEnter:EnterLeaveEventPlugin, onMouseLeave:EnterLeaveEventPlugin, ... }

registrationNameDependencies, 这个对象记录了React事件到原生事件的映射

css

复制代码

{ onBlur: ['blur'], onClick: ['click'], onClickCapture: ['click'], onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'], onMouseEnter: ['mouseout', 'mouseover'], onMouseLeave: ['mouseout', 'mouseover'], }

plugins对象, 记录了所有注册的插件列表

ini

复制代码

plugins = [LegacySimpleEventPlugin, LegacyEnterLeaveEventPlugin, ...]


为什么针对同一个事件,即使可能存在多次回调,document(container)也只需要注册一次监听

因为React注册到document(container)上的并不是一个某个DOM节点具体的回调逻辑,而是一个统一的事件分发函数dispatchEvent - > 事件委托思想

dispatchEvent是怎么实现事件分发的

事件触发的本质是对dispatchEvent函数的调用

React事件处理为什么要手动绑定this

react组件会被编译为React.createElement,在createElement中,它的this丢失了,并不是由组件实例调用的,因此需要手动绑定this

为什么不能通过return false阻止事件的默认行为

因为React基于浏览器的事件机制实现了一套自己的事件机制,和原生DOM事件不同,它采用了事件委托的思想,通过dispatch统一分发事件处理函数

React怎么阻止事件冒泡

  • 阻止合成事件的冒泡用e.stopPropagation()
  • 阻止合成事件和最外层document事件冒泡,使用e.nativeEvent.stopImmediatePropogation()
  • 阻止合成事件和除了最外层document事件冒泡,通过判断e.target避免

javascript

复制代码

document.body.addEventListener('click',e=>{ if(e.target && e.target.matches('div.stop')){ return } this.setState({active:false}) })

HOC和hooks的区别

useEffect和useLayoutEffect区别

React性能优化手段

  1. shouldComponentUpdate
  2. memo
  3. getDerviedStateFromProps
  4. 使用Fragment
  5. v-for使用正确的key
  6. 拆分尽可能小的可复用组件,ErrorBoundary
  7. 使用React.lazy和React.Suspense延迟加载不需要立马使用的组件

六、常用组件

错误边界

React部分组件的错误不应该导致整个应用崩溃,为了解决这个问题,React16引入了错误边界

使用方法:

React组件在内部定义了getDerivedStateFromError或者componentDidCatch,它就是一个错误边界。getDerviedStateFromError和componentDidCatch的区别是前者展示降级UI,后者记录具体的错误信息,它只能用于class组件

scala

复制代码

import React from "react" class ErrorBoundary extends React.Component{ constructor(props){ super(props) this.state={ hasError:false } } staic getDerivedStateFromError(){ return { hasError:true} } componentDidCatch(err,info){ console.error(err,info) } render(){ if(this.state.hasError){ return <div>Oops,err</div> } return this.props.children } } // App.jsx import React from "react" import ErrorBoundary from "./components/ErrorBoundary" import ComponentA from "./components/ComponentA" export class App extends React.Component{ render(){ return ( <ErrorBoundary> <ComponentA></ComponentA> </ErrorBoundary> ) } }

错误边界无法捕获自身的错误,也无法捕获事件处理、异步代码(setTimeout、requestAnimationFrame)、服务端渲染的错误

Portal

Portal提供了让子组件渲染在除了父组件之外的DOM节点的方式,它接收两个参数,第一个是需要渲染的React元素,第二个是渲染的地方(DOM元素)

scss

复制代码

ReactDOM.createPortal(child,container)

用途:弹窗、提示框等

Fragment

Fragment提供了一种将子列表分组又不产生额外DOM节点的方法

Context

常规的组件数据传递是使用props,当一个嵌套组件向另一个嵌套组件传递数据时,props会被传递很多层,很多不需要用到props的组件也引入了数据,会造成数据来源不清晰,多余的变量定义等问题,Context提供了一种跨层级组件数据传递的方法

scala

复制代码

const ThemeContext = React.createContext('light') class App extends React.Component { render(){ return( <ThemeContext.Provider value="dark"> <ToolBar/> </ThemeContext> ) } } function ToolBar(){ return <div> <ThemeButton/> </div> } class ThemeButton extends React.Component { static contextType = ThemeContext render(){ return <Button theme={this.context}></Button> } }

Suspense

Suspense使组件允许在某些操作结束后再进行渲染,比如接口请求,一般与React.lazy一起使用

Transition

Transition是React18引入的一个并发特性,允许操作被中断,避免回到可见内容的Suspense降级方案

七、Redux工作原理

Redux是一个状态管理库,使用场景:

  • 跨层级组件数据共享与通信

  • 一些需要持久化的全局数据,比如用户登录信息

Redux工作原理

使用单例模式实现

Store 一个全局状态管理对象

Reducer 一个纯函数,根据旧state和props更新新state

Action 改变状态的唯一方式是dispatch action

Redux 遵循的三个原则是什么?

  1. 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。

  2. 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。

  3. 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

你对“单一事实来源”有什么理解?

Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。

八、React-Router工作原理

为什么需要前端路由
  1. 早期:一个页面对应一个路由,路由跳转导致页面刷新,用户体验差
  2. ajax的出现使得不刷新页面也可以更新页面内容,出现了SPA(单页应用)。SPA不能记住用户操作,只有一个页面对URL做映射,SEO不友好
  3. 前端路由帮助我们在仅有一个页面时记住用户进行了哪些操作
前端路由解决了什么问题
  1. 当用户刷新页面,浏览器会根据当前URL对资源进行重定向(发起请求)
  2. 单页面对服务端来说就是一套资源,怎么做到不同的URL映射不同的视图内容
  3. 拦截用户的刷新操作,避免不必要的资源请求;感知URL的变化

react-router-dom有哪些组件

HashRouter/BrowserRouter 路由器

Route 路由匹配

Link 链接,在html中是个锚点

NavLink 当前活动链接

Switch 路由跳转

Redirect 路由重定向

ini

复制代码

<Link to="/home">Home</Link> <NavLink to="/abount" activeClassName="active">About</NavLink> <Redirect to="/dashboard">Dashboard</Redirect>

React Router核心能力:跳转

路由负责定义路径和组件的映射关系

导航负责触发路由的改变
路由器根据Route定义的映射关系为新的路径匹配对应的逻辑

BrowserRouter使用的HTML5history api实现路由跳转
HashRouter使用URL的hash属性控制路由跳转

前端通用路由解决方案

  • hash模式

改变URL以#分割的路径字符串,让页面感知路由变化的一种模式,通过hashchange事件触发

  • history模式

通过浏览器的history api实现,通过popState事件触发

九、数据如何在React组件中流动

React组件通信

react组件通信方式有哪些

组件通信的方式有很多种,可以分为以下几种:

  1. 父组件向子组件通信
  2. 子组件向父组件通信
  3. 兄弟组件通信
  4. 父组件向后代组件通信
  5. 无关组件通信

父组件向子组件通信

  • props传递,利用React单向数据流的思想,通过props传递

javascript

复制代码

function Child(props){ return ( <div className="child"> <input value={props.name} type="text"/> </div> ) } const Parent = <Child name="我是儿子"/>

子组件向父组件通信

  • 回调函数

父组件向子组件传递一个函数,通过函数回调,拿到子组件传过来的值

scala

复制代码

import React from "react" class Parent extends React.Component{ constructor(){ super() this.state={ price:0 } } getPrice(val){ this.setState({ price:val }) } render(){ return (<div> <span className="label">价格:</span> <span className="value">{this.state.price}</span> <Child getPrice={this.getPrice.bind(this)}/> </div>) } } class Child extends React.Component{ getItemPrice(e){ this.props.getPrice(e) } render(){ return ( <div> <button onClick={this.getItemPrice.bind(this)}>廓形大衣</button> <button onClick={this.getItemPrice.bind(this)>牛仔裤</button> </div> ) } }

  • 事件冒泡

点击子组件的button按钮,事件会冒泡到父组件上

ini

复制代码

const Child = () => { return <button>点击</button>; }; const Parent = () => { const sayName = name => { console.log(name); }; return ( <div onClick={() => sayName('lyllovelemon')}> <Child /> </div> ); }; export default Parent;

  • Ref

scala

复制代码

import React from "react" class Parent extends React.Component{ constructor(props){ super(props) this.myRef=React.createRef() } componentDidMount(){ this.myRef.current.changeVal('lyllovelemon') } } class Child extends React.Component{ constructor(props){ super(props) } changeVal(name){ console.log(name) } render(){ return (<div></div>) } }

兄弟组件通信

实际上就是通过父组件中转数据的,子组件a传递给父组件,父组件再传递给子组件b

javascript

复制代码

import React from "react" class Parent extends React.Component{ constructor(props){ super(props) this.state={ count:0 } } increment(){ this.setState({ count:this.state.count+1 }) } render(){ return ( <div> <ChildOne count={this.state.count} /> <ChildTwo onClick={this.increment} /> </div> ) } }

父组件向后代组件通信

Context

scala

复制代码

import React from "react" const PriceContext = React.createContext("price") export default class Parent extends React.Component{ constructor(props){ super(props) } render(){ return ( <PriceContext.Provider value={200}> </PriceContext> ) } } class Child extends React.Component{ ... } class SubChild extends React.Component{ constructor(props){ super(props) } render(){ return ( <PriceContext.Consumer> { price=> <div>price:{price}</div> } </PriceContext.Consumner> ) } }

HOC

Redux

ref,useRef,forwardRef,useImperativeHandle

十、React Hooks

React hooks解决了什么问题

在React16.8以前,常用的组件写法有class组件和function组件

javascript

复制代码

class Demo extends React.Component{ constructor(props){ super(props) this.state={ name:'lyllovelemon' } } changeName(){ this.setState({ name:'lylmusiclover' }) } render(){ return ( <div> {this.state.name} <button onClick={this.changeName.bind(this)}>换名</button> </div> ) } } function Demo2({name,setName}){ return <div> {name} <button onClick={()=>setName('哈哈哈')}>换名</button> </div> } //App.jsx function App() { const [name,setName]=useState('lyl') return ( <div className="App"> <Demo/> <Demo2 setName={setName} name={name}/> </div> ) }

函数组件与类组件的区别:

  1. 类组件需要声明constructor,函数组件不需要
  2. 类组件需要手动绑定this,函数组件不需要
  3. 类组件有生命周期钩子,函数组件没有
  4. 类组件可以定义并维护自己的state,属于有状态组件,函数组件是无状态组件
  5. 类组件需要继承class,函数组件不需要
  6. 类组件使用的是面向对象的方法,封装:组件属性和方法都封装在组件内部 继承:通过extends React.Component继承;函数组件使用的是函数式编程思想

why React hooks

优点:

  1. 告别难以理解的class组件
  2. 解决业务逻辑难以拆分的问题
  3. 使状态逻辑复用变的简单可行
  4. 函数组件从设计理念来看,更适合react

局限性:

  1. hooks还不能完整的为函数组件提供类组件的能力
  2. 函数组件给了我们一定程度的自由,却也对开发者的水平提出了更高的要求
  3. Hooks 在使用层面有着严格的规则约束

常用hooks

useState

juejin.cn/post/711893…

十一、SetState是同步还是异步的

setState是一个异步方法,但是在setTimeout/setInterval等定时器里逃脱了React对它的掌控,变成了同步方法

实现机制类似于vue的$nextTick和浏览器的事件循环机制,每个setState都会被react加入到任务队列,多次对同一个state使用setState只会返回最后一次的结果,因为它不是立刻就更新,而是先放在队列中,等时机成熟在执行批量更新。React18以后,使用了createRoot api后,所有setState都是异步批量执行的

十二、fiber架构

什么是fiber,fiber解决了什么问题

在React16以前,React更新是通过树的深度优先遍历完成的,遍历是不能中断的,当树的层级深就会产生栈的层级过深,页面渲染速度变慢的问题,为了解决这个问题引入了fiber,React fiber就是虚拟DOM,它是一个链表结构,返回了return、children、siblings,分别代表父fiber,子fiber和兄弟fiber,随时可中断

Fiber是纤程,比线程更精细,表示对渲染线程实现更精细的控制

应用目的
实现增量渲染,增量渲染指的是把一个渲染任务分解为多个渲染任务,而后将其分散到多个帧里。增量渲染是为了实现任务的可中断、可恢复,并按优先级处理任务,从而达到更顺滑的用户体验

Fiber的可中断、可恢复怎么实现的

fiber是协程,是比线程更小的单元,可以被人为中断和恢复,当react更新时间超过1帧时,会产生视觉卡顿的效果,因此我们可以通过fiber把浏览器渲染过程分段执行,每执行一会就让出主线程控制权,执行优先级更高的任务

fiber是一个链表结构,它有三个指针,分别记录了当前节点的下一个兄弟节点,子节点,父节点。当遍历中断时,它是可以恢复的,只需要保留当前节点的索引,就能根据索引找到对应的节点

Fiber更新机制

初始化

  1. 创建fiberRoot(React根元素)和rootFiber(通过ReactDOM.render或者ReactDOM.createRoot创建出来的)
  2. 进入beginWork

workInProgress:正在内存中构建的fiber树叫workInProgress fiber,在第一次更新时,所有的更新都发生在workInProgress树,在第一次更新后,workInProgress树上的状态是最新状态,它会替换current树

current:正在视图层渲染的树叫current fiber树

ini

复制代码

currentFiber.alternate = workInProgressFiber workInProgressFiber.alternate = currentFiber

  1. 深度调和子节点,渲染视图

在新建的alternate树上,完成整个子节点的遍历,包括fiber的创建,最后会以workInProgress树最为最新的渲染树,fiberRoot的current指针指向workInProgress使其变成current fiber,完成初始化流程

更新

  1. 重新创建workInProgress树,复用当前current树上的alternate,作为新的workInProgress

渲染完成后,workInProgress树又变成current树

双缓冲模式

话剧演出中,演员需要切换不同的场景,以一个一小时话剧来说,在舞台中切换场景,时间来不及。一般是准备两个舞台,切换场景从左边舞台到右边舞台演出

在计算机图形领域,通过让图形硬件交替读取两套缓冲数据,可以实现画面的无缝切换,减少视觉的抖动甚至卡顿。

react的current树和workInProgress树使用双缓冲模式,可以减少fiber节点的开销,减少性能损耗

React渲染流程

如图,React用JSX描述页面,JSX经过babel编译为render function,执行后产生VDOM,VDOM不是直接渲染的,会先转换为fiber,再进行渲染。vdom转换为fiber的过程叫reconcile,转换过程会创建DOM,全部转换完成后会一次性commit到DOM,这个过程不是一次性的,而是可打断的,这就是fiber架构的渲染流程

vdom(React Element对象)中只记录了子节点,没有记录兄弟节点,因此渲染不可打断

fiber(fiberNode对象)是一个链表,它记录了父节点、兄弟节点、子节点,因此是可以打断的

21. 详细解释 React 组件的生命周期方法。

一些最重要的生命周期方法是:

  1. componentWillMount() – 在渲染之前执行,在客户端和服务器端都会执行。
  2. componentDidMount() – 仅在第一次渲染后在客户端执行。
  3. componentWillReceiveProps() – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
  4. shouldComponentUpdate() – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
  5. componentWillUpdate() – 在 DOM 中进行渲染之前调用。
  6. componentDidUpdate() – 在渲染发生后立即调用。
  7. componentWillUnmount() – 从 DOM 卸载组件后调用。用于清理内存空间。

22. React中的事件是什么?

在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:

  1. 用驼峰命名法对事件命名而不是仅使用小写字母。
  2. 事件作为函数而不是字符串传递。

事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。

23. 如何在React中创建一个事件?

class Display extends React.Component({    
    show(evt) {
        // code   
    },   
    render() {      
        // Render the div with an onClick prop (value is a function)        
        return (            
            <div onClick={this.show}>Click Me!</div>
        );    
    }
});
复制代码

24. React中的合成事件是什么?

合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。

29. 你对受控组件和非受控组件了解多少?

受控组件

非受控组件

  1. 没有维持自己的状态

  2. 保持着自己的状态

2.数据由父组件控制

2.数据由 DOM 控制

  1. 通过 props 获取当前值,然后通过回调通知更改

  2. Refs 用于获取其当前值

30. 什么是高阶组件(HOC)?

高阶组件是重用组件逻辑的高级方法,是一种源于 React 的组件模式。 HOC 是自定义组件,在它之内包含另一个组件。它们可以接受子组件提供的任何动态,但不会修改或复制其输入组件中的任何行为。你可以认为 HOC 是“纯(Pure)”组件。

31. 你能用HOC做什么?

HOC可用于许多任务,例如:

  • 代码重用,逻辑和引导抽象
  • 渲染劫持
  • 状态抽象和控制
  • Props 控制

32. 什么是纯组件?

纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能。

33. React 中 key 的重要性是什么?

key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能。

React Redux

34. MVC框架的主要问题是什么?

以下是MVC框架的一些主要问题:

  • 对 DOM 操作的代价非常高
  • 程序运行缓慢且效率低下
  • 内存浪费严重
  • 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建

35. 解释一下 Flux

flux

Flux 是一种强制单向数据流的架构模式。它控制派生数据,并使用具有所有数据权限的中心 store 实现多个组件之间的通信。整个应用中的数据更新必须只能在此处进行。 Flux 为应用提供稳定性并减少运行时的错误。

36. 什么是Redux?

Redux 是一个使用叫做 “action” 的事件来管理和更新应用状态的模式和工具库 它以集中式 Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。

37. Redux遵循的三个原则是什么?

  1. 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
  2. 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
  3. 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

41. 如何在 Redux 中定义 Action?

React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建。以下是 Action 和Action Creator 的示例:

function addTodo(text) {
       return {
                type: ADD_TODO,    
                 text
    }
}
复制代码

42. 解释 Reducer 的作用。

Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

43. Store 在 Redux 中的意义是什么?

Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

45. Redux 有哪些优点?

Redux 的优点如下:

  • 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。
  • 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。
  • 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。
  • 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
  • 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。
  • 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。
  • 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单。