写点什么

关于前端面试你需要知道的知识点

作者:beifeng1996
  • 2022-10-31
    浙江
  • 本文字数:9916 字

    阅读完需:约 33 分钟

如何在 ReactJS 的 Props 上应用验证?

当应用程序在开发模式下运行时,React 将自动检查咱们在组件上设置的所有 props,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 propsisRequired定义的。下面是一组预定义的 prop 类型:


  • React.PropTypes.string

  • React.PropTypes.number

  • React.PropTypes.func

  • React.PropTypes.node

  • React.PropTypes.bool 例如,咱们为用户组件定义了如下的propTypes

React 中 props.children 和 React.Children 的区别

在 React 中,当涉及组件嵌套,在父组件中使用props.children把所有子组件显示出来。如下:


function ParentComponent(props){    return (        <div>            {props.children}        </div>    )}
复制代码


如果想把父组件中的属性传给所有的子组件,需要使用React.Children方法。


比如,把几个 Radio 组合起来,合成一个 RadioGroup,这就要求所有的 Radio 具有同样的 name 属性值。可以这样:把 Radio 看做子组件,RadioGroup 看做父组件,name 的属性值在 RadioGroup 这个父组件中设置。


首先是子组件:


//子组件function RadioOption(props) {  return (    <label>      <input type="radio" value={props.value} name={props.name} />      {props.label}    </label>  )}
复制代码


然后是父组件,不仅需要把它所有的子组件显示出来,还需要为每个子组件赋上 name 属性和值:


//父组件用,props是指父组件的propsfunction renderChildren(props) {
//遍历所有子组件 return React.Children.map(props.children, child => { if (child.type === RadioOption) return React.cloneElement(child, { //把父组件的props.name赋值给每个子组件 name: props.name }) else return child })}//父组件function RadioGroup(props) { return ( <div> {renderChildren(props)} </div> )}function App() { return ( <RadioGroup name="hello"> <RadioOption label="选项一" value="1" /> <RadioOption label="选项二" value="2" /> <RadioOption label="选项三" value="3" /> </RadioGroup> )}export default App;
复制代码


以上,React.Children.map让我们对父组件的所有子组件又更灵活的控制。

constructor 为什么不先渲染?

由 ES6 的继承规则得知,不管子类写不写 constructor,在 new 实例的过程都会给补上 constructor。


所以:constructor 钩子函数并不是不可缺少的,子组件可以在一些情况略去。比如不自己的 state,从 props 中获取的情况

对 React-Intl 的理解,它的工作原理?

React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 ReactJS 绑定。


React-intl 提供了两种使用方法,一种是引用 React 组件,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者,只有在无法使用 React 组件的地方,才应该调用框架提供的 API。它提供了一系列的 React 组件,包括数字格式化、字符串格式化、日期格式化等。


在 React-intl 中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。

用户不同权限 可以查看不同的页面 如何实现?

  1. Js 方式

  2. 根据用户权限类型,把菜单配置成 json, 没有权限的直接不显示

  3. react-router 方式 在 route 标签上 添加 onEnter 事件,进入路由之前替换到首页


<Route path="/home" component={App} onEnter={(nexState,replace)=>{      if(nexState.location.pathname!=='/'){         var  sid = UtilsMoudle.getSidFromUrl(nexState);         if(!sid){            replace("/")         }else{            console.log(sid);         }      }    }}>
复制代码


  1. 自己封装一个 privateRouter 组件 里面判断是否有权限,有的话返回

  2. 没有权限的话 component 返回一个提示信息的组件。

  3. 扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?

  4. react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回 null

  5. vue 可以使用自定义指令,如果没有权限移除组件


// 需要在入口处添加自定义权限指令v-auth,显示可操作组件Vue.directive('auth', {    bind: function (el, binding, vnode) {        // 用户权限表        const rules = auths        for (let i = 0; i < rules.length; i++) {            const item = rules[i]            if(!binding.value || (binding.value == item.auth)){                // 权限允许则显示组件                return true            }        }        // 移除组件        el.parentNode.removeChild(el)    }})// 使用<template>  <div>    <Button v-auth="admin_user_add">添加用户</Button>    <Button v-auth="admin_user_del">删除用户</Button>    <Button v-auth="admin_user_edit">编辑用户</Button>  </div></template>
复制代码

constructor

答案是:在 constructor 函数里面,需要用到props的值的时候,就需要调用 super(props)
复制代码


  1. class 语法糖默认会帮你定义一个 constructor,所以当你不需要使用 constructor 的时候,是可以不用自己定义的

  2. 当你自己定义一个 constructor 的时候,就一定要写 super(),否则拿不到 this

  3. 当你在 constructor 里面想要使用 props 的值,就需要传入 props 这个参数给 super,调用 super(props),否则只需要写 super()

为什么列表循环渲染的 key 最好不要用 index

举例说明


变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3
复制代码


  • 那么 diff 算法在变化前的数组找到 key =0 的值是 1,在变化后数组里找到的 key=0 的值是 4

  • 因为子元素不一样就重新删除并更新

  • 但是如果加了唯一的 key,如下


变化前数组的值是[1,2,3,4],key就是对应的下标:id0,id1,id2,id3变化后数组的值是[4,3,2,1],key对应的下标也是:id3,id2,id1,id0
复制代码


  • 那么 diff 算法在变化前的数组找到 key =id0 的值是 1,在变化后数组里找到的 key=id0 的值也是 1

  • 因为子元素相同,就不删除并更新,只做移动操作,这就提升了性能


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

React 的严格模式如何使用,有什么用处?

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。可以为应用程序的任何部分启用严格模式。例如:


import React from 'react';function ExampleApplication() {  return (    <div>      <Header />      <React.StrictMode>                <div>          <ComponentOne />          <ComponentTwo />        </div>      </React.StrictMode>            <Footer />    </div>  );}

复制代码


在上述的示例中,不会对 HeaderFooter 组件运行严格模式检查。但是,ComponentOneComponentTwo 以及它们的所有后代元素都将进行检查。


StrictMode 目前有助于:


  • 识别不安全的生命周期

  • 关于使用过时字符串 ref API 的警告

  • 关于使用废弃的 findDOMNode 方法的警告

  • 检测意外的副作用

  • 检测过时的 context API

React Hook 的使用限制有哪些?

React Hooks 的限制主要有两条:


  • 不要在循环、条件或嵌套函数中调用 Hook;

  • 在 React 的函数组件中调用 Hook。


那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。


  • 组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。

  • 复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。

  • 人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,希望在编译优化层面做出一些改进。


这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks 基于函数组件开始设计。然而第三个问题决定了 Hooks 只支持函数组件。


那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。


这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。

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

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


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

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

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

Redux 原理及工作流程

(1)原理 Redux 源码主要分为以下几个模块文件


  • compose.js 提供从右到左进行函数式编程

  • createStore.js 提供作为生成唯一 store 的函数

  • combineReducers.js 提供合并多个 reducer 的函数,保证 store 的唯一性

  • bindActionCreators.js 可以让开发者在不直接接触 dispacth 的前提下进行更改 state 的操作

  • applyMiddleware.js 这个方法通过中间件来增强 dispatch 的功能


const actionTypes = {    ADD: 'ADD',    CHANGEINFO: 'CHANGEINFO',}
const initState = { info: '初始化',}
export default function initReducer(state=initState, action) { switch(action.type) { case actionTypes.CHANGEINFO: return { ...state, info: action.preload.info || '', } default: return { ...state }; }}
export default function createStore(reducer, initialState, middleFunc) {
if (initialState && typeof initialState === 'function') { middleFunc = initialState; initialState = undefined; }
let currentState = initialState;
const listeners = [];
if (middleFunc && typeof middleFunc === 'function') { // 封装dispatch return middleFunc(createStore)(reducer, initialState); }
const getState = () => { return currentState; }
const dispatch = (action) => { currentState = reducer(currentState, action);
listeners.forEach(listener => { listener(); }) }
const subscribe = (listener) => { listeners.push(listener); }
return { getState, dispatch, subscribe }}
复制代码


(2)工作流程


  • const store= createStore(fn)生成数据;

  • action: {type: Symble('action01), payload:'payload' }定义行为;

  • dispatch 发起 action:store.dispatch(doSomething('action001'));

  • reducer:处理 action,返回新的 state;


通俗点解释:


  • 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法

  • 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State

  • State—旦有变化,Store 就会调用监听函数,来更新 View


以 store 为核心,可以把它看成数据存储中心,但是他要更改数据的时候不能直接修改,数据修改更新的角色由 Reducers 来担任,store 只做存储,中间人,当 Reducers 的更新完成以后会通过 store 的订阅来通知 react component,组件把新的状态重新获取渲染,组件中也能主动发送 action,创建 action 后这个动作是不会执行的,所以要 dispatch 这个 action,让 store 通过 reducers 去做更新 React Component 就是 react 的每个组件。

React Hooks 和生命周期的关系?

函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 useStateuseEffect()useLayoutEffect()


即:Hooks 组件(使用了 Hooks 的函数组件)有生命周期,而函数组件(未使用 Hooks 的函数组件)是没有生命周期的


下面是具体的 class 与 Hooks 的生命周期对应关系


  • constructor:函数组件不需要构造函数,可以通过调用 **useState 来初始化 state**。如果计算的代价比较昂贵,也可以传一个函数给 useState


const [num, UpdateNum] = useState(0)
复制代码


  • getDerivedStateFromProps:一般情况下,我们不需要使用它,可以在渲染过程中更新 state,以达到实现 getDerivedStateFromProps 的目的。


function ScrollView({row}) {  let [isScrollingDown, setIsScrollingDown] = useState(false);  let [prevRow, setPrevRow] = useState(null);  if (row !== prevRow) {    // Row 自上次渲染以来发生过改变。更新 isScrollingDown。    setIsScrollingDown(prevRow !== null && row > prevRow);    setPrevRow(row);  }  return `Scrolling down: ${isScrollingDown}`;}
复制代码


React 会立即退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。


  • shouldComponentUpdate:可以用 **React.memo** 包裹一个组件来对它的 props 进行浅比较


const Button = React.memo((props) => {  // 具体的组件});
复制代码


注意:**React.memo 等效于 **``**PureComponent**,它只浅比较 props。这里也可以使用 useMemo 优化每一个节点。


  • render:这是函数组件体本身。

  • componentDidMount, componentDidUpdateuseLayoutEffect 与它们两的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffectuseEffect 可以表达所有这些的组合。


// componentDidMountuseEffect(()=>{  // 需要在 componentDidMount 执行的内容}, [])useEffect(() => {   // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容  document.title = `You clicked ${count} times`;   return () => {    // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)    // 以及 componentWillUnmount 执行的内容         } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关}, [count]); // 仅在 count 更改时更新
复制代码


请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 ,因此会使得额外操作很方便


  • componentWillUnmount:相当于 useEffect 里面返回的 cleanup 函数


// componentDidMount/componentWillUnmountuseEffect(()=>{  // 需要在 componentDidMount 执行的内容  return function cleanup() {    // 需要在 componentWillUnmount 执行的内容        }}, [])
复制代码


  • componentDidCatch and getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会加上。


React 中的 setState 和 replaceState 的区别是什么?

(1)setState() setState()用于设置状态对象,其语法如下:


setState(object nextState[, function callback])
复制代码


  • nextState,将要设置的新状态,该状态会和当前的 state 合并

  • callback,可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用。


合并 nextState 和当前 state,并重新渲染组件。setState 是 React 事件处理函数中和请求回调函数中触发 UI 更新的主要方法。


(2)replaceState() replaceState()方法与 setState()类似,但是方法只会保留 nextState 中状态,原 state 不在 nextState 中的状态都会被删除。其语法如下:


replaceState(object nextState[, function callback])
复制代码


  • nextState,将要设置的新状态,该状态会替换当前的 state。

  • callback,可选参数,回调函数。该函数会在 replaceState 设置成功,且组件重新渲染后调用。


总结: setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,不会减少原来的状态。而 replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了。

在 React 中组件的 props 改变时更新组件的有哪些方法?

在一个组件传入的 props 更新时重新渲染该组件常用的方法是在componentWillReceiveProps中将新的 props 更新到组件的 state 中(这种 state 被成为派生状态(Derived State)),从而实现重新渲染。React 16.3 中还引入了一个新的钩子函数getDerivedStateFromProps来专门实现这一需求。


(1)componentWillReceiveProps(已废弃)


在 react 的 componentWillReceiveProps(nextProps)生命周期中,可以在子组件的 render 函数执行前,通过 this.props 获取旧的属性,通过 nextProps 获取新的 props,对比两次 props 是否相同,从而更新子组件自己的 state。


这样的好处是,可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。


(2)getDerivedStateFromProps(16.3 引入)


这个生命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使用componentWillReceiveProps时,就可以考虑使用getDerivedStateFromProps来进行替代。


两者的参数是不相同的,而getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过 this 访问到 class 的属性,也并不推荐直接访问属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,根据新传入的 props 来映射到 state。


需要注意的是,如果 props 传入的内容不需要影响到你的 state,那么就需要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的末尾:


static getDerivedStateFromProps(nextProps, prevState) {    const {type} = nextProps;    // 当传入的type发生变化的时候,更新state    if (type !== prevState.type) {        return {            type,        };    }    // 否则,对于state不进行任何操作    return null;}
复制代码

什么是高阶组件

高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件


  • 属性代理 (Props Proxy) 在我看来属性代理就是提取公共的数据和方法到父组件,子组件只负责渲染数据,相当于设计模式里的模板模式,这样组件的重用性就更高了


function proxyHoc(WrappedComponent) {    return class extends React.Component {        render() {            const newProps = {                count: 1            }            return <WrappedComponent {...this.props} {...newProps} />        }    }}
复制代码


  • 反向继承


const MyContainer = (WrappedComponent)=>{    return class extends WrappedComponent {        render(){            return super.render();        }    }}
复制代码

当渲染一个列表时,何为 key?设置 key 的目的是什么

Keys 会有助于 React 识别哪些 items 改变了,被添加了或者被移除了。Keys 应该被赋予数组内的元素以赋予(DOM)元素一个稳定的标识,选择一个 key 的最佳方法是使用一个字符串,该字符串能惟一地标识一个列表项。很多时候你会使用数据中的 IDs 作为 keys,当你没有稳定的 IDs 用于被渲染的 items 时,可以使用项目索引作为渲染项的 key,但这种方式并不推荐,如果 items 可以重新排序,就会导致 re-render 变慢。

setState 是同步异步?为什么?实现原理?

1. setState 是同步执行的


setState 是同步执行的,但是 state 并不一定会同步更新


2. setState 在 React 生命周期和合成事件中批量覆盖执行


在 React 的生命周期钩子和合成事件中,多次执行 setState,会批量执行


具体表现为,多次同步执行的 setState,会进行合并,类似于 Object.assign,相同的 key,后面的会覆盖前面的


当遇到多个 setState 调用时候,会提取单次传递 setState 的对象,把他们合并在一起形成一个新的


单一对象,并用这个单一的对象去做 setState 的事情,就像 Object.assign 的对象合并,后一个


key 值会覆盖前面的 key 值


经过 React 处理的事件是不会同步更新 this.state 的. 通过 addEventListener || setTimeout/setInterval 的方式处理的则会同步更新。


为了合并 setState,我们需要一个队列来保存每次 setState 的数据,然后在一段时间后执行合并操作和更新 state,并清空这个队列,然后渲染组件。

Redux 请求中间件如何处理并发

使用 redux-Saga redux-saga 是一个管理 redux 应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将 react 中的同步操作与异步操作区分开来,以便于后期的管理与维护。 redux-saga 如何处理并发:


  • takeEvery


可以让多个 saga 任务并行被 fork 执行。


import {    fork,    take} from "redux-saga/effects"
const takeEvery = (pattern, saga, ...args) => fork(function*() { while (true) { const action = yield take(pattern) yield fork(saga, ...args.concat(action)) }})
复制代码


  • takeLatest


takeLatest 不允许多个 saga 任务并行地执行。一旦接收到新的发起的 action,它就会取消前面所有 fork 过的任务(如果这些任务还在执行的话)。在处理 AJAX 请求的时候,如果只希望获取最后那个请求的响应, takeLatest 就会非常有用。


import {    cancel,    fork,    take} from "redux-saga/effects"
const takeLatest = (pattern, saga, ...args) => fork(function*() { let lastTask while (true) { const action = yield take(pattern) if (lastTask) { yield cancel(lastTask) // 如果任务已经结束,则 cancel 为空操作 } lastTask = yield fork(saga, ...args.concat(action)) }})
复制代码

hooks 父子传值

父传子在父组件中用useState声明数据 const [ data, setData ] = useState(false)
把数据传递给子组件<Child data={data} />
子组件接收export default function (props) { const { data } = props console.log(data)}子传父子传父可以通过事件方法传值,和父传子有点类似。在父组件中用useState声明数据 const [ data, setData ] = useState(false)
把更新数据的函数传递给子组件<Child setData={setData} />
子组件中触发函数更新数据,就会直接传递给父组件export default function (props) { const { setData } = props setData(true)}如果存在多个层级的数据传递,也可依照此方法依次传递
// 多层级用useContextconst User = () => { // 直接获取,不用回调 const { user, setUser } = useContext(UserContext); return <Avatar user={user} setUser={setUser} />;};
复制代码

在 React 中遍历的方法有哪些?

(1)遍历数组:map && forEach


import React from 'react';
class App extends React.Component { render() { let arr = ['a', 'b', 'c', 'd']; return ( <ul> { arr.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> ) }}
class App extends React.Component { render() { let arr = ['a', 'b', 'c', 'd']; return ( <ul> { arr.forEach((item, index) => { return <li key={index}>{item}</li> }) } </ul> ) }}

复制代码


(2)遍历对象:map && for in


class App extends React.Component {  render() {    let obj = {      a: 1,      b: 2,      c: 3    }    return (      <ul>        {          (() => {            let domArr = [];            for(const key in obj) {              if(obj.hasOwnProperty(key)) {                const value = obj[key]                domArr.push(<li key={key}>{value}</li>)              }            }            return domArr;          })()        }      </ul>    )  }}
// Object.entries() 把对象转换成数组class App extends React.Component { render() { let obj = { a: 1, b: 2, c: 3 } return ( <ul> { Object.entries(obj).map(([key, value], index) => { // item是一个数组,把item解构,写法是[key, value] return <li key={key}>{value}</li> }) } </ul> ) }}

复制代码


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

发布
暂无评论
关于前端面试你需要知道的知识点_React_beifeng1996_InfoQ写作社区