写点什么

一天梳理完 react 面试高频题

作者:beifeng1996
  • 2022 年 10 月 11 日
    浙江
  • 本文字数:7229 字

    阅读完需:约 24 分钟

redux 中间件

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


  • redux-logger:提供日志输出

  • redux-thunk:处理异步操作

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

如何配置 React-Router 实现路由切换

(1)使用<Route> 组件


路由匹配是通过比较 <Route> 的 path 属性和当前地址的 pathname 来实现的。当一个 <Route> 匹配成功时,它将渲染其内容,当它不匹配时就会渲染 null。没有路径的 <Route> 将始终被匹配。


// when location = { pathname: '/about' }<Route path='/about' component={About}/> // renders <About/><Route path='/contact' component={Contact}/> // renders null<Route component={Always}/> // renders <Always/>
复制代码


(2)结合使用 <Switch> 组件和 <Route> 组件


<Switch> 用于将 <Route> 分组。


<Switch>    <Route exact path="/" component={Home} />    <Route path="/about" component={About} />    <Route path="/contact" component={Contact} /></Switch>
复制代码


<Switch> 不是分组 <Route> 所必须的,但他通常很有用。 一个 <Switch> 会遍历其所有的子 <Route>元素,并仅渲染与当前地址匹配的第一个元素。


(3)使用 <Link>、 <NavLink>、<Redirect> 组件


<Link> 组件来在你的应用程序中创建链接。无论你在何处渲染一个<Link> ,都会在应用程序的 HTML 中渲染锚(<a>)。


<Link to="/">Home</Link>   // <a href='/'>Home</a>
复制代码


是一种特殊类型的 当它的 to 属性与当前地址匹配时,可以将其定义为"活跃的"。


// location = { pathname: '/react' }<NavLink to="/react" activeClassName="hurray">    React</NavLink>// <a href='/react' className='hurray'>React</a>
复制代码


当我们想强制导航时,可以渲染一个<Redirect>,当一个<Redirect>渲染时,它将使用它的 to 属性进行定向。

React-Router 如何获取 URL 的参数和历史对象?

(1)获取 URL 的参数


  • get 传值


路由配置还是普通的配置,如:'admin',传参方式如:'admin?id='1111''。通过this.props.location.search获取 url 获取到一个字符串'?id='1111' 可以用 url,qs,querystring,浏览器提供的 api URLSearchParams 对象或者自己封装的方法去解析出 id 的值。


  • 动态路由传值


路由需要配置成动态路由:如path='/admin/:id',传参方式,如'admin/111'。通过this.props.match.params.id 取得 url 中的动态路由 id 部分的值,除此之外还可以通过useParams(Hooks)来获取


  • 通过 query 或 state 传值


传参方式如:在 Link 组件的 to 属性中可以传递对象{pathname:'/admin',query:'111',state:'111'};。通过this.props.location.statethis.props.location.query来获取即可,传递的参数可以是对象、数组等,但是存在缺点就是只要刷新页面,参数就会丢失。


(2)获取历史对象


  • 如果 React >= 16.8 时可以使用 React Router 中提供的 Hooks


import { useHistory } from "react-router-dom";let history = useHistory();
复制代码


2.使用 this.props.history 获取历史对象


let history = this.props.history;
复制代码

React 设计思路,它的理念是什么?

(1)编写简单直观的代码


React 最大的价值不是高性能的虚拟 DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。react 文档第一条就是声明式,React 使创建交互式 UI 变得轻而易举。为应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。 以声明式编写 UI,可以让代码更加可靠,且方便调试。


(2)简化可复用的组件


React 框架里面使用了简化的组件模型,但更彻底地使用了组件化的概念。React 将整个 UI 上的每一个功能模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。React 的组件具有如下的特性∶


  • 可组合:简单组件可以组合为复杂的组件

  • 可重用:每个组件都是独立的,可以被多个组件使用

  • 可维护:和组件相关的逻辑和 UI 都封装在了组件的内部,方便维护

  • 可测试:因为组件的独立性,测试组件就变得方便很多。


(3) Virtual DOM


真实页面对应一个 DOM 树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新。 DOM 操作非常昂贵。在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目的代码变得难 以维护。React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM,每次数据更新后,重新计算 Virtual DOM,并和上一次生成的 Virtual DOM 做对比,对发生变化的部分做批量更新。React 也提供了直观的 shouldComponentUpdate 生命周期回调,来减少数据变化后不必要的 Virtual DOM 对比过程,以保证性能。


(4)函数式编程


React 把过去不断重复构建 UI 的过程抽象成了组件,且在给定参数的情况下约定渲染对应的 UI 界面。React 能充分利用很多函数式方法去减少冗余代码。此外,由于它本身就是简单函数,所以易于测试。


(5)一次学习,随处编写


无论现在正在使用什么技术栈,都可以随时引入 React 来开发新特性,而不需要重写现有代码。


React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。因为 React 组件可以映射为对应的原生控件。在输出的时候,是输出 Web DOM,还是 Android 控件,还是 iOS 控件,就由平台本身决定了。所以,react 很方便和其他平台集成

React 组件命名推荐的方式是哪个?

通过引用而不是使用来命名组件 displayName。


使用 displayName 命名组件:


export default React.createClass({  displayName: 'TodoApp',  // ...})
复制代码


React 推荐的方法:


export default class TodoApp extends React.Component {  // ...}
复制代码

在 React 中,何为 state

State 和 props 类似,但它是私有的,并且完全由组件自身控制。State 本质上是一个持有数据,并决定组件如何渲染的对象。

props 是什么

  • react 的核心思想是组件化,页面被分成很多个独立,可复用的组件

  • 而组件就是一个函数,可以接受一个参数作为输入值,这个参数就是 props,所以 props 就是从外部传入组件内部的数据

  • 由于 react 的单向数据流模式,所以 props 是从父组件传入子组件的数据

Redux 中异步的请求怎么处理

可以在 componentDidmount 中直接进⾏请求⽆须借助 redux。但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助 redux 的异步中间件进⾏异步处理。redux 异步流中间件其实有很多,当下主流的异步中间件有两种 redux-thunk、redux-saga。


(1)使用 react-thunk 中间件


redux-thunk 优点:


  • 体积⼩: redux-thunk 的实现⽅式很简单,只有不到 20⾏代码

  • 使⽤简单: redux-thunk 没有引⼊像 redux-saga 或者 redux-observable 额外的范式,上⼿简单


redux-thunk 缺陷:


  • 样板代码过多: 与 redux 本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的

  • 耦合严重: 异步操作与 redux 的 action 偶合在⼀起,不⽅便管理

  • 功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装


使用步骤:


  • 配置中间件,在 store 的创建中配置


import {createStore, applyMiddleware, compose} from 'redux';import reducer from './reducer';import thunk from 'redux-thunk'
// 设置调试工具const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;// 设置中间件const enhancer = composeEnhancers( applyMiddleware(thunk));
const store = createStore(reducer, enhancer);
export default store;
复制代码


  • 添加一个返回函数的 actionCreator,将异步请求逻辑放在里面


/**  发送get请求,并生成相应action,更新store的函数  @param url {string} 请求地址  @param func {function} 真正需要生成的action对应的actionCreator  @return {function} */// dispatch为自动接收的store.dispatch函数 export const getHttpAction = (url, func) => (dispatch) => {    axios.get(url).then(function(res){        const action = func(res.data)        dispatch(action)    })}
复制代码


  • 生成 action,并发送 action


componentDidMount(){    var action = getHttpAction('/getData', getInitTodoItemAction)    // 发送函数类型的action时,该action的函数体会自动执行    store.dispatch(action)}
复制代码


(2)使用 redux-saga 中间件


redux-saga 优点:


  • 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中

  • action 摆脱 thunk function: dispatch 的参数依然是⼀个纯粹的 action (FSA),⽽不是充满 “⿊魔法” thunk function

  • 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理

  • 功能强⼤: redux-saga 提供了⼤量的 Saga 辅助函数和 Effect 创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤

  • 灵活: redux-saga 可以将多个 Saga 可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步 flow

  • 易测试,提供了各种 case 的测试⽅案,包括 mock task,分⽀覆盖等等


redux-saga 缺陷:


  • 额外的学习成本: redux-saga 不仅在使⽤难以理解的 generator function,⽽且有数⼗个 API,学习成本远超 redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与 redux-observable 不同,redux-observable 虽然也有额外学习成本但是背后是 rxjs 和⼀整套思想

  • 体积庞⼤: 体积略⼤,代码近 2000⾏,min 版 25KB 左右

  • 功能过剩: 实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码

  • ts⽀持不友好: yield⽆法返回 TS 类型


redux-saga 可以捕获 action,然后执行一个函数,那么可以把异步代码放在这个函数中,使用步骤如下:


  • 配置中间件


import {createStore, applyMiddleware, compose} from 'redux';import reducer from './reducer';import createSagaMiddleware from 'redux-saga'import TodoListSaga from './sagas'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers( applyMiddleware(sagaMiddleware));
const store = createStore(reducer, enhancer);sagaMiddleware.run(TodoListSaga)
export default store;
复制代码


  • 将异步请求放在 sagas.js 中


import {takeEvery, put} from 'redux-saga/effects'import {initTodoList} from './actionCreator'import {GET_INIT_ITEM} from './actionTypes'import axios from 'axios'
function* func(){ try{ // 可以获取异步返回数据 const res = yield axios.get('/getData') const action = initTodoList(res.data) // 将action发送到reducer yield put(action) }catch(e){ console.log('网络请求失败') }}
function* mySaga(){ // 自动捕获GET_INIT_ITEM类型的action,并执行func yield takeEvery(GET_INIT_ITEM, func)}
export default mySaga
复制代码


  • 发送 action


componentDidMount(){  const action = getInitTodoItemAction()  store.dispatch(action)}
复制代码

什么是 React Fiber?

Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟 DOM 的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中。


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

diff 虚拟 DOM 比较的规则

  • 【旧虚拟 DOM】 与 【新虚拟 DOM】中相同 key

  • 若虚拟 DOM 中的内容没有发生改变,直接使用旧的虚拟 DOM

  • 若虚拟 DOM 中的内容发生改变了,则生成新真实的 DOM,随后替换页面中之前的真实 DOM

  • 【旧虚拟 DOM】 中未找到 与 【新虚拟 DOM】相同的 key

  • 根据数据创建真实 DOM,随后渲染到页面

什么是 state

  • 在组件初始化的时候 通过 this.state 给组件设置一个初始化的 state,第一次 render 的时候会用 state 来渲染组件

  • 通过 this.setState 方法来更新 state

React-Router 4 怎样在路由变化时重新渲染同一个组件?

当路由变化时,即组件的 props 发生了变化,会调用 componentWillReceiveProps 等生命周期钩子。那需要做的只是: 当路由改变时,根据路由,也去请求数据:


class NewsList extends Component {  componentDidMount () {     this.fetchData(this.props.location);  }
fetchData(location) { const type = location.pathname.replace('/', '') || 'top' this.props.dispatch(fetchListData(type)) } componentWillReceiveProps(nextProps) { if (nextProps.location.pathname != this.props.location.pathname) { this.fetchData(nextProps.location); } } render () { ... }}
复制代码


利用生命周期 componentWillReceiveProps,进行重新 render 的预处理操作。

React 16 中新生命周期有哪些

关于 React16 开始应用的新生命周期: 可以看出,React16 自上而下地对生命周期做了另一种维度的解读:


  • Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(我们后面会重点讲解)是有关的;

  • Pre-commit 阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新真实的 DOM,不过 DOM 信息已经是可以读取的了;

  • Commit 阶段:在这一步,React 会完成真实 DOM 的更新工作。Commit 阶段,我们可以拿到真实 DOM(包括 refs)。


与此同时,新的生命周期在流程方面,仍然遵循“挂载”、“更新”、“卸载”这三个广义的划分方式。它们分别对应到:


  • 挂载过程:

  • constructor

  • getDerivedStateFromProps

  • render

  • componentDidMount

  • 更新过程:

  • getDerivedStateFromProps

  • shouldComponentUpdate

  • render

  • getSnapshotBeforeUpdate

  • componentDidUpdate

  • 卸载过程:

  • componentWillUnmount

React-Router 怎么设置重定向?

使用<Redirect>组件实现路由的重定向:


<Switch>  <Redirect from='/users/:id' to='/users/profile/:id'/>  <Route path='/users/profile/:id' component={Profile}/></Switch>
复制代码


当请求 /users/:id 被重定向去 '/users/profile/:id'


  • 属性 from: string:需要匹配的将要被重定向路径。

  • 属性 to: string:重定向的 URL 字符串

  • 属性 to: object:重定向的 location 对象

  • 属性 push: bool:若为真,重定向操作将会把新地址加入到访问历史记录里面,并且无法回退到前面的页面。

react 中 key 的作用

简单的说:key 是虚拟 DOM 中的一种标识,在更新显示是 key 起到了极其重要的作用


复杂的说:当状态中的数据发生改变的时候,react 会根据【新数据】生成【新的虚拟 DOM】,随后 react 进行【新虚拟 DOM】 和 【旧的虚拟 DOM】的 diff 比较,而在这个比较过程中 key 就是起到是关键中用

如何将两个或多个组件嵌入到一个组件中?

可以通过以下方式将组件嵌入到一个组件中:


class MyComponent extends React.Component{    render(){        return(                      <div>                <h1>Hello</h1>                <Header/>            </div>        );    }}class Header extends React.Component{    render(){        return            <h1>Header Component</h1>      };}ReactDOM.render(    <MyComponent/>, document.getElementById('content'));
复制代码

如何有条件地向 React 组件添加属性?

对于某些属性,React 非常聪明,如果传递给它的值是虚值,可以省略该属性。例如:


var InputComponent = React.createClass({  render: function () {    var required = true;    var disabled = false;    return <input type="text" disabled={disabled} required={required} />;  },});
复制代码


渲染结果:


<input type="text" required>
复制代码


另一种可能的方法是:


var condition = true;var component = <div value="foo" {...(condition && { disabled: true })} />;
复制代码

diff 算法如何比较?

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

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

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

  • 单节点 diff

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

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

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

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

在调用 setState 之后发生了什么

  • 状态合并,触发调和:

  • setState 函数之后,会将传入的参数对象与当前的状态合并,然后出发调用过程

  • 根据新的状态构建虚拟 dom 树

  • 经过调和过程,react 会高效的根据新的状态构建虚拟 DOM 树,准备渲染整个 UI 页面

  • 计算新老树节点差异,最小化渲染

  • 得倒新的虚拟 DOM 树后,会计算出新老树的节点差异,会根据差异对界面进行最小化渲染

  • 按需更新

  • 在差异话计算中,react 可以相对准确的知道哪些位置发生了改变以及该如何改变,这保证按需更新,而不是宣布重新渲染

Redux 中的 connect 有什么作用

connect 负责连接 React 和 Redux


(1)获取 state


connect 通过 context 获取 Provider 中的 store,通过 store.getState() 获取整个 store tree 上所有 state


(2)包装原组件


将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回—个 ReactComponent 对 象 Connect,Connect 重 新 render 外部传入的原组件 WrappedComponent ,并把 connect 中传入的 mapStateToProps,mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent


(3)监听 store tree 变化


connect 缓存了 store tree 中 state 的状态,通过当前 state 状态 和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染

用户头像

beifeng1996

关注

还未添加个人签名 2022.09.01 加入

还未添加个人简介

评论

发布
暂无评论
一天梳理完react面试高频题_React_beifeng1996_InfoQ写作社区