写点什么

字节前端面试被问到的 react 问题

作者:beifeng1996
  • 2022-11-01
    浙江
  • 本文字数:6044 字

    阅读完需:约 20 分钟

redux 中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制可以让我们改变数据流,实现如异步actionaction 过滤,日志输出,异常报告等功能


  • redux-logger:提供日志输出

  • redux-thunk:处理异步操作

  • redux-promise:处理异步操作,actionCreator的返回值是promise

React 中 refs 的作用是什么?有哪些应用场景?

Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:


  • 处理焦点、文本选择或者媒体的控制

  • 触发必要的动画

  • 集成第三方 DOM 库


Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给其实例属性:


class MyComponent extends React.Component {  constructor(props) {    super(props)    this.myRef = React.createRef()  }  render() {    return <div ref={this.myRef} />  }}
复制代码


由于函数组件没有实例,因此不能在函数组件上直接使用 ref


function MyFunctionalComponent() {  return <input />;}class Parent extends React.Component {  constructor(props) {    super(props);    this.textInput = React.createRef();  }  render() {    // 这将不会工作!    return (      <MyFunctionalComponent ref={this.textInput} />    );  }}
复制代码


但可以通过闭合的帮助在函数组件内部进行使用 Refs:


function CustomTextInput(props) {  // 这里必须声明 textInput,这样 ref 回调才可以引用它  let textInput = null;  function handleClick() {    textInput.focus();  }  return (    <div>      <input        type="text"        ref={(input) => { textInput = input; }} />      <input        type="button"        value="Focus the text input"        onClick={handleClick}      />    </div>  );  }
复制代码


注意:


  • 不应该过度的使用 Refs

  • ref 的返回值取决于节点的类型:

  • ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为他的 current 属性以创建 ref

  • ref 属性被用于一个自定义的类组件时,ref 对象将接收该组件已挂载的实例作为他的 current

  • 当在父组件中需要访问子组件中的 ref 时可使用传递 Refs 或回调 Refs。

diff 算法如何比较?

  • 只对同级比较,跨层级的 dom 不会进行复用

  • 不同类型节点生成的 dom 树不同,此时会直接销毁老节点及子孙节点,并新建节点

  • 可以通过 key 来对元素 diff 的过程提供复用的线索

  • 单节点 diff

  • 单点 diff 有如下几种情况:

  • key 和 type 相同表示可以复用节点

  • key 不同直接标记删除节点,然后新建节点

  • key 相同 type 不同,标记删除该节点和兄弟节点,然后新创建节点

如何解决 props 层级过深的问题

  • 使用 Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递 props;

  • 使用 Redux 等状态库。

React 的事件和普通的 HTML 事件有什么不同?

区别:


  • 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;

  • 对于事件函数处理语法,原生事件为字符串,react 事件为函数;

  • react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用preventDefault()来阻止默认行为。


合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:


  • 兼容所有浏览器,更好的跨平台;

  • 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。

  • 方便 react 统一管理和事务机制。


事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行。

react-router 里的 Link 标签和 a 标签的区别

从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>是 react-router 里实现路由跳转的链接,一般配合<Route> 使用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route>对应的页面内容更新,而不会刷新整个页面。


<Link>做了 3 件事情:


  • 有 onclick 那就执行 onclick

  • click 的时候阻止 a 标签默认事件

  • 根据跳转 href(即是 to),用 history (web 前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而<a>标签就是普通的超链接了,用于从当前页面跳转到 href 指向的另一 个页面(非锚点情况)。


a 标签默认事件禁掉之后做了什么才实现了跳转?


let domArr = document.getElementsByTagName('a')[...domArr].forEach(item=>{    item.addEventListener('click',function () {        location.href = this.href    })})
复制代码

React-Router 4 的 Switch 有什么用?

Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route><Redirect>,它里面不能放其他元素。


假如不加 <Switch>


import { Route } from 'react-router-dom'
<Route path="/" component={Home}></Route><Route path="/login" component={Login}></Route>
复制代码


Route 组件的 path 属性用于匹配路径,因为需要匹配 /Home,匹配 /loginLogin,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" /><Route path="/login" /> 都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch> 来做到只显示一个匹配组件:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route path="/" component={Home}></Route> <Route path="/login" component={Login}></Route></Switch>
复制代码


此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是精确匹配路径,经常与<Switch> 联合使用。只有当 URL 和该 <Route> 的 path 属性完全一致的情况下才能匹配上:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route exact path="/" component={Home}></Route> <Route exact path="/login" component={Login}></Route></Switch>
复制代码


参考:前端react面试题详细解答

redux 有什么缺点

  • 一个组件所需要的数据,必须由父组件传过来,而不能像 flux 中直接从 store 取

  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新 render,可能会有效率影响,或者需要写复杂的 shouldComponentUpdate 进行判断

react 有什么优点

  • 提高应用性能

  • 可以方便的在客户端和服务端使用

  • 使用 jsx 模板进行数据渲染,可读性好

createElement 过程

React.createElement(): 根据指定的第一个参数创建一个 React 元素


React.createElement(  type,  [props],  [...children])
复制代码


  • 第一个参数是必填,传入的是似 HTML 标签名称,eg: ul, li

  • 第二个参数是选填,表示的是属性,eg: className

  • 第三个参数是选填, 子节点,eg: 要显示的文本内容


//写法一:
var child1 = React.createElement('li', null, 'one'); var child2 = React.createElement('li', null, 'two'); var content = React.createElement('ul', { className: 'teststyle' }, child1, child2); // 第三个参数可以分开也可以写成一个数组 ReactDOM.render( content, document.getElementById('example') );
//写法二:
var child1 = React.createElement('li', null, 'one'); var child2 = React.createElement('li', null, 'two'); var content = React.createElement('ul', { className: 'teststyle' }, [child1, child2]); ReactDOM.render( content, document.getElementById('example') );
复制代码

Redux 内部原理 内部怎么实现 dispstch 一个函数的

redux-thunk中间件作为例子,下面就是thunkMiddleware函数的代码


// 部分转为ES5代码,运行middleware函数会返回一个新的函数,如下:return ({ dispatch, getState }) => {    // next实际就是传入的dispatch    return function (next) {        return function (action) {            // redux-thunk核心            if (typeof action === 'function') {                 return action(dispatch, getState, extraArgument);            }            return next(action);        };    };}
复制代码


redux-thunk库内部源码非常的简单,允许action是一个函数,同时支持参数传递,否则调用方法不变


  • redux创建Store:通过combineReducers函数合并reducer函数,返回一个新的函数combination(这个函数负责循环遍历运行reducer函数,返回全部state)。将这个新函数作为参数传入createStore函数,函数内部通过 dispatch,初始化运行传入的combination,state 生成,返回 store 对象

  • redux中间件:applyMiddleware函数中间件的主要目的就是修改dispatch函数,返回经过中间件处理的新的dispatch函数

  • redux使用:实际就是再次调用循环遍历调用reducer函数,更新state

什么是 React Context?

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。

Dva 工作原理

集成redux+redux-saga


工作原理


改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State

mobox 和 redux 有什么区别?

(1)共同点


  • 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;

  • 某一状态只有一个可信数据来源(通常命名为 store,指状态容器);

  • 操作更新状态方式统一,并且可控(通常以 action 方式提供更新状态的途径);

  • 支持将 store 与 React 组件连接,如 react-redux,mobx- react;


(2)区别 Redux 更多的是遵循 Flux 模式的一种实现,是一个 JavaScript 库,它关注点主要是以下几方面∶


  • Action∶ 一个 JavaScript 对象,描述动作相关信息,主要包含 type 属性和 payload 属性∶

  • Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;

  • Store∶ 管理 action 和 reducer 及其关系的对象,主要提供以下功能∶

  • 异步流∶ 由于 Redux 所有对 store 状态的变更,都应该通过 action 触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入 React 组件中,就需要使用其他框架配合管理异步任务流程,如 redux-thunk,redux-saga 等;


Mobx 是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶


  • Action∶定义改变状态的动作函数,包括如何变更状态;

  • Store∶ 集中管理模块状态(State)和动作(action)

  • Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据


对比总结:


  • redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中

  • redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作

  • redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改

  • mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用

  • mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

react-redux 的实现原理?

通过 redux 和 react context 配合使用,并借助高阶函数,实现了 react-redux

React 中 refs 干嘛用的?

Refs 提供了一种访问在render方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props 是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 Refs。咱们可以在组件添加一个 ref 属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。


class UnControlledForm extends Component {  handleSubmit = () => {    console.log("Input Value: ", this.input.value);  };  render() {    return (      <form onSubmit={this.handleSubmit}>        <input type="text" ref={(input) => (this.input = input)} />        <button type="submit">Submit</button>      </form>    );  }}
复制代码


请注意,input 元素有一个ref属性,它的值是一个函数。该函数接收输入的实际 DOM 元素,然后将其放在实例上,这样就可以在 handleSubmit 函数内部访问它。 经常被误解的只有在类组件中才能使用 refs,但是refs也可以通过利用 JS 中的闭包与函数组件一起使用。


function CustomForm({ handleSubmit }) {  let inputElement;  return (    <form onSubmit={() => handleSubmit(inputElement.value)}>      <input type="text" ref={(input) => (inputElement = input)} />      <button type="submit">Submit</button>    </form>  );}
复制代码

根据下面定义的代码,可以找出存在的两个问题吗 ?

请看下面的代码:



答案:1.在构造函数没有将 props 传递给 super,它应该包括以下行


constructor(props) {super(props);// ...}
复制代码


2.事件监听器(通过addEventListener()分配时)的作用域不正确,因为 ES6 不提供自动绑定。因此,开发人员可以在构造函数中重新分配clickHandler来包含正确的绑定:


constructor(props) {super(props);this.clickHandler = this.clickHandler.bind(this);// ...}
复制代码

非嵌套关系组件的通信方式?

即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。


  • 可以使用自定义事件通信(发布订阅模式)

  • 可以通过 redux 等进行全局状态管理

  • 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。

为什么不直接更新 state 呢 ?

如果试图直接更新 state ,则不会重新渲染组件。


// 错误This.state.message = 'Hello world';
复制代码


需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应:


// 正确做法This.setState({message: ‘Hello World’});
复制代码

react16 版本的 reconciliation 阶段和 commit 阶段是什么

  • reconciliation 阶段包含的主要工作是对 current tree 和 new tree 做 diff 计算,找出变化部分。进行遍历、对比等是可以中断,歇一会儿接着再来。

  • commit 阶段是对上一阶段获取到的变化部分应用到真实的 DOM 树中,是一系列的 DOM 操作。不仅要维护更复杂的 DOM 状态,而且中断后再继续,会对用户体验造成影响。在普遍的应用场景下,此阶段的耗时比 diff 计算等耗时相对短。


用户头像

beifeng1996

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
字节前端面试被问到的react问题_React_beifeng1996_InfoQ写作社区