关于react.js:关于前端面试你需要知道的知识点

46次阅读

共计 13123 个字符,预计需要花费 33 分钟才能阅读完成。

如何在 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 组件 外面判断是否有权限,有的话返回
    <Route path={path} component={component} exact={exact}/>
    没有权限的话 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>
    )
  }
}

正文完
 0