传入 setstate函数的第二个参数的作用是什么?
第二个参数是一个函数,该函数会在 setState函数调用完成并且组件开始重渲染时调用,可以用该函数来监听渲染是否完成。
代码语言:javascript复制this.setstate(
{
username: "有课前端网",
},
() => console.log("re-rendered success. ")
);
vue 或者react 优化整体优化
- 虚拟dom
为什么虚拟 dom 会提高性能?(必考)
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
前端react面试题详细解答
React如何获取组件对应的DOM元素?
可以用ref来获取某个子节点的实例,然后通过当前class组件实例的一些特定属性来直接获取子节点实例。ref有三种实现方法:
- 字符串格式:字符串格式,这是React16版本之前用得最多的,例如:
<p ref="info">span</p>
- 函数格式:ref对应一个方法,该方法有一个参数,也就是对应的节点实例,例如:
<p ref={ele => this.info = ele}></p>
- createRef方法:React 16提供的一个API,使用React.createRef()来实现
什么是受控组件和非受控组件
- 受状态控制的组件,必须要有onChange方法,否则不能使用 受控组件可以赋予默认值(官方推荐使用 受控组件) 实现双向数据绑定
class Input extends Component{
constructor(){
super();
this.state = {val:'100'}
}
handleChange = (e) =>{ //e是事件源
let val = e.target.value;
this.setState({val});
};
render(){
return (<div>
<input type="text" value={this.state.val} onChange={this.handleChange}/>
{this.state.val}
</div>)
}
}
- 非受控也就意味着我可以不需要设置它的state属性,而通过ref来操作真实的DOM
class Sum extends Component{
constructor(){
super();
this.state = {result:''}
}
//通过ref设置的属性 可以通过this.refs获取到对应的dom元素
handleChange = () =>{
let result = this.refs.a.value this.b.value;
this.setState({result});
};
render(){
return (
<div onChange={this.handleChange}>
<input type="number" ref="a"/>
{/*x代表的真实的dom,把元素挂载在了当前实例上*/}
<input type="number" ref={(x)=>{
this.b = x;
}}/>
{this.state.result}
</div>
)
}
}
React.createClass和extends Component的区别有哪些?
React.createClass和extends Component的bai区别主要在于:
(1)语法区别
- createClass本质上是一个工厂函数,extends的方式更加接近最新的ES6规范的class写法。两种方式在语法上的差别主要体现在方法的定义和静态属性的声明上。
- createClass方式的方法定义使用逗号,隔开,因为creatClass本质上是一个函数,传递给它的是一个Object;而class的方式定义方法时务必谨记不要使用逗号隔开,这是ES6 class的语法规范。
(2)propType 和 getDefaultProps
- React.createClass:通过proTypes对象和getDefaultProps()方法来设置和获取props.
- React.Component:通过设置两个属性propTypes和defaultProps
(3)状态的区别
- React.createClass:通过getInitialState()方法返回一个包含初始值的对象
- React.Component:通过constructor设置初始状态
(4)this区别
- React.createClass:会正确绑定this
- React.Component:由于使用了 ES6,这里会有些微不同,属性并不会自动绑定到 React 类的实例上。
(5)Mixins
- React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。
- 如果使用 ES6 的方式来创建组件,那么
React mixins
的特性将不能被使用了。
React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代
这三者是目前react解决代码复用的主要方式:
- 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
- render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
- 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderltem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。
(1)HOC 官方解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
简言之,HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。
代码语言:javascript复制// hoc的定义
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的逻辑处理
render() {
// ... 并使用新数据渲染被包装的组件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
HOC的优缺点∶
- 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。
- 缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖
(2)Render props 官方解释∶
"render prop"是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,"render"的命名可以是任何其他有效的标识符。
代码语言:javascript复制// DataProvider组件内部的渲染逻辑如下
class DataProvider extends React.Components {
state = {
name: 'Tom'
}
render() {
return (
<div>
<p>共享数据组件自己内部的渲染逻辑</p>
{ this.props.render(this.state) } </div>
);
}
}
// 调用方式
<DataProvider render={data => (
<h1>Hello {data.name}</h1>
)}/>
由此可以看到,render props的优缺点也很明显∶
- 优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
- 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅
(3)Hooks 官方解释∶
代码语言:javascript复制Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。
// 自定义一个获取订阅数据的hook
function useSubscription() {
const data = DataSource.getComments();
return [data];
}
//
function CommentList(props) {
const {data} = props;
const [subData] = useSubscription();
...
}
// 使用
<CommentList data='hello' />
以上可以看出,hook解决了hoc的prop覆盖的问题,同时使用的方式解决了render props的嵌套地狱的问题。hook的优点如下∶
- 使用直观;
- 解决hoc的prop 重名问题;
- 解决render props 因共享数据 而出现嵌套地狱的问题;
- 能在return之外使用数据的问题。
需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。、
React 组件中怎么做事件代理?它的原理是什么?
React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。
在React底层,主要对合成事件做了两件事:
- 事件委派: React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。
- 自动绑定: React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。
React如何判断什么时候重新渲染组件?
组件状态的改变可以因为props
的改变,或者直接通过setState
方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的shouldComponentUpdate
方法默认返回true
,这就是导致每次更新都重新渲染的原因。
当React将要渲染组件时会执行shouldComponentUpdate
方法来看它是否返回true
(组件应该更新,也就是重新渲染)。所以需要重写shouldComponentUpdate
方法让它根据情况返回true
或者false
来告诉React什么时候重新渲染什么时候跳过重新渲染。
React组件的state和props有什么区别?
(1)props
props是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。
(2)state
state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。
(3)区别
- props 是传递给组件的(类似于函数的形参),而state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
- props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
- state 是在组件中创建的,一般在 constructor中初始化 state。state 是多变的、可以修改,每次setState都异步更新的。
React setState 调用之后发生了什么?是同步还是异步?
(1)React中setState后发生了什么
在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。
在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。
(2)setState 是同步还是异步的
假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。所以默认是异步的,但是在一些情况下是同步的。
setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。
- 异步: 在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
- 同步: 在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener 、setTimeout、setInterval 等事件中,就只能同步更新。
一般认为,做异步设计是为了性能优化、减少渲染次数:
setState
设计为异步,可以显著的提升性能。如果每次调用setState
都进行一次更新,那么意味着render
函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新;- 如果同步更新了
state
,但是还没有执行render
函数,那么state
和props
不能保持同步。state
和props
不能保持一致性,会在开发中产生很多的问题;
React 16.X 中 props 改变后在哪个生命周期中处理
在getDerivedStateFromProps中进行处理。
这个生命周期函数是为了替代componentWillReceiveProps
存在的,所以在需要使用componentWillReceiveProps
时,就可以考虑使用getDerivedStateFromProps
来进行替代。
两者的参数是不相同的,而getDerivedStateFromProps
是一个静态函数,也就是这个函数不能通过this访问到class的属性,也并不推荐直接访问属性。而是应该通过参数提供的nextProps以及prevState来进行判断,根据新传入的props来映射到state。
需要注意的是,如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾:
代码语言:javascript复制static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// 当传入的type发生变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进行任何操作
return null;
}
React的事件和普通的HTML事件有什么不同?
区别:
- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
- 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()
来阻止默认行为。
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
- 兼容所有浏览器,更好的跨平台;
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
- 方便 react 统一管理和事务机制。
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。
react16的错误边界(Error Boundaries)是什么
部分 UI 中的 JavaScript 错误不应该破坏整个应用程序。 为了解决 React 用户的这个问题,React 16引入了一个 “错误边界(Error Boundaries)” 的新概念。
代码语言:javascript复制import React from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends React.Component{
constructor(props) {
super(props);
this.state={hasError:false};
}
componentDidCatch(err,info) {
this.setState({hasError: true});
}
render() {
if (this.state.hasError) {
return <h1>Something Went Wrong</h1>
}
return this.props.children;
}
}
class Page extends React.Component{
render() {
return (
<ErrorBoundary>
<Clock/>
</ErrorBoundary>
)
}
}
class Clock extends React.Component{
render() {
return (
<div>hello{null.toString()}</div>
)
}
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
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 中 refs 的作用是什么
Refs
是React
提供给我们的安全访问DOM
元素或者某个组件实例的句柄- 可以为元素添加
ref
属性然后在回调函数中接受该元素在DOM
树中的句柄,该值会作为回调函数的第一个参数返回
React 中 keys 的作用是什么?
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
在 React 中渲染集合时,向每个重复的元素添加关键字对于帮助React跟踪元素与数据之间的关联非常重要。key 应该是唯一ID,最好是 UUID 或收集项中的其他唯一字符串:
代码语言:javascript复制<ul>
{todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)};
</ul>
在集合中添加和删除项目时,不使用键或将索引用作键会导致奇怪的行为。
redux的三大原则
- 单一数据源
整个应用的state被存储在一个object tree中,并且这个object tree 之存在唯一一个store中
- state是只读的
唯一改变state的方式是触发action,action是一个用于描述已经发生时间的对象,这个保证了视图和网络请求都不能直接修改state,相反他们只能表达想要修改的意图
- 使用纯函数来执行修改state 为了描述action如何改变state tree 需要编写reduce
使用状态要注意哪些事情?
要注意以下几点。
- 不要直接更新状态
- 状态更新可能是异步的
- 状态更新要合并。
- 数据从上向下流动
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及其子组件的重新渲染
React 事件机制
代码语言:javascript复制<div onClick={this.handleClick.bind(this)}>点我</div>
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document
上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document
上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation
是无效的,而应该调用 event.preventDefault
。
实现合成事件的目的如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。