2021前端react面试题汇总
1. mobx 和 redux 有什么区别?
(1)共同点
- 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;
- 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
- 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
- 支持将store与React组件连接,如react-redux,mobx- react;
(2)区别 Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶
Action∶ 一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性∶
o type∶ action 类型; o payload∶ 负载数据; 复制代码
- Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;
Store∶ 管理action和reducer及其关系的对象,主要提供以下功能∶
o 维护应用状态并支持访问状态(getState()); o 支持监听action的分发,更新状态(dispatch(action)); o 支持订阅store的变更(subscribe(listener)); 复制代码
- 异步流∶ 由于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提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
2. Redux 和 Vuex 有什么区别,它们的共同思想
(1)Redux 和 Vuex区别
- Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
- Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
- Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)
通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;
(2)共同思想
- 单—的数据源
- 变化可以预测
本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。
3. Redux 中间件是怎么拿到store 和 action? 然后怎么处理?
redux中间件本质就是一个函数柯里化。redux applyMiddleware Api 源码中每个middleware 接受2个参数, Store 的getState 函数和dispatch 函数,分别获得store和action,最终返回一个函数。该函数会被传入 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是({ getState,dispatch })=> next => action。
4. 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及其子组件的重新渲染
5. React Hooks 解决了哪些问题?
React Hooks 主要解决了以下问题:
(1)在组件之间复用状态逻辑很难
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)解决此类问题可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这可能会很麻烦,并且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。
可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
(2)复杂组件变得难以理解
在组件中,每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
(3)难以理解的 class
除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术
6. 为什么React并不推荐优先考虑使用Context?
- Context目前还处于实验阶段,可能会在后面的发行版本中有很大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来大的影响和麻烦,不建议在app中使用context。
- 尽管不建议在app中使用context,但是独有组件而言,由于影响范围小于app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用context
- 对于组件之间的数据通信或者状态管理,有效使用props或者state解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑context。
- context的更新需要通过setState()触发,但是这并不是很可靠的,Context支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回false 那么不能保证Context的更新一定可以使用Context的子组件,因此,Context的可靠性需要关注
7. React中什么是受控组件和非控组件?
(1)受控组件 在使用表单来收集用户输入时,例如等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的value或checked属性相对应,react通过这种方式消除了组件的局部状态,使整个状态可控。react官方推荐使用受控表单组件。
受控组件更新state的流程:
- 可以通过初始state中设置表单的默认值
- 每当表单的值发生变化时,调用onChange事件处理器
- 事件处理器通过事件对象e拿到改变后的状态,并更新组件的state
- 一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新
受控组件缺陷: 表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。
(2)非受控组件 如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。
React官方的解释:
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref来从 DOM 节点中获取表单数据。 因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
例如,下面的代码在非受控组件中接收单个属性:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
);
}
}
复制代码
总结: 页面中所有输入类的DOM如果是现用现取的称为非受控组件,而通过setState将输入的值维护到了state中,需要时再从state中取出,这里的数据就受到了state的控制,称为受控组件。
8. 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
}
}
复制代码
由于函数组件没有实例,因此不能在函数组件上直接使用 ref
:
function MyFunctionalComponent() {
return ;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// 这将不会工作!
return (
);
}
}
复制代码
但可以通过闭合的帮助在函数组件内部进行使用 Refs:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 回调才可以引用它
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
{ textInput = input; }} />
);
}
复制代码
注意:
- 不应该过度的使用 Refs
ref
的返回值取决于节点的类型:
- 当
ref
属性被用于一个普通的 HTML 元素时,React.createRef()
将接收底层 DOM 元素作为他的current
属性以创建ref
。 - 当
ref
属性被用于一个自定义的类组件时,ref
对象将接收该组件已挂载的实例作为他的current
。
- 当
当在父组件中需要访问子组件中的
ref
时可使用传递 Refs 或回调 Refs。9. React组件的构造函数有什么作用?它是必须的吗?
构造函数主要用于两个目的:
- 通过将对象分配给this.state来初始化本地状态
将事件处理程序方法绑定到实例上
所以,当在React class中需要设置state的初始值或者绑定事件时,需要加上构造函数,官方Demo:
class LikeButton extends React.Component { constructor() { super(); this.state = { liked: false }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({liked: !this.state.liked}); } render() { const text = this.state.liked ? 'liked' : 'haven\'t liked'; return (
You {text} this. Click to toggle.); } } ReactDOM.render(, document.getElementById('example') );
构造函数用来新建父类的this对象;子类必须在constructor方法中调用super方法;否则新建实例时会报错;因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法;子类就得不到this对象。
**注意:**
- constructor () 必须配上 super(), 如果要在constructor 内部使用 this.props 就要 传入props , 否则不用
- JavaScript中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在constructor中绑定事件
### 10. React.forwardRef是什么?它有什么作用?
React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs