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

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

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

  • React.PropTypes.string

  • React.PropTypes.number

  • React.PropTypes.func

  • React.PropTypes.node

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

    import PropTypes from "prop-types";
    class User extends React.Component {
      render() {
        return (
          <>
            <h1>Welcome, {this.props.name}</h1>
            <h2>Age, {this.props.age}</h2>
          </>
        );
      }
    }
    
    User.propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number.isRequired,
    };
    

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是指父组件的props
function 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方式
    根据用户权限类型,把菜单配置成json, 没有权限的直接不显示
  2. 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组件 里面判断是否有权限,有的话返回

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

  2. 扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?
    react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回null
    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 可以表达所有这些的组合。
// componentDidMount
useEffect(()=>{
  // 需要在 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/componentWillUnmount
useEffect(()=>{
  // 需要在 componentDidMount 执行的内容
  return function cleanup() {
    // 需要在 componentWillUnmount 执行的内容      
  }
}, [])

  • componentDidCatch and getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会加上。
class 组件 Hooks 组件
constructor useState
getDerivedStateFromProps useState 里面 update 函数
shouldComponentUpdate useMemo
render 函数本身
componentDidMount useEffect
componentDidUpdate useEffect
componentWillUnmount useEffect 里面返回的函数
componentDidCatch
getDerivedStateFromError

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)
}
如果存在多个层级的数据传递,也可依照此方法依次传递

// 多层级用useContext
const 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>
    )
  }
}


posted @ 2022-10-31 08:38  beifeng11996  阅读(31)  评论(0编辑  收藏  举报