react面试题

react中的数据都是不可变的给数据新增的时候不能使用pushsplice等因为他们会改变原始数据 

什么是JSX

一种把js和html混合书写的语法,这个语法浏览器是不支持的

其实jsx只是一种语法糖,最终会通过babel转译成createElement语法

createElement方法最终会返回一个js对象(虚拟dom),后面会通过render方法将虚拟dom转成真实的dom挂载到页面中

 对虚拟dom的理解

虚拟 DOM 是 JS 对象,这个对象是对真实 DOM 的描述。

虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。

虚拟 DOM 解决的关键问题有以下两个:

  1. 研发体验/研发效率的问题:虚拟dom的出现为数据驱动视图这一思想提供了载体,使得前端开发能够基于函数式编程方式实现声明式编程。
  2. 跨平台的问题:虚拟 DOM,它描述的东西可以是真实 DOM,也可以是iOS 界面、安卓界面、小程序......同一套虚拟 DOM,可以对接不同平台的渲染逻辑,从而实现“一次编码,多端运行”。

什么是纯函数

  • 给定相同的输入,将始终返回相同的输出。
  • 无副作用。
  1. 不能修改参数的值
  2. 不能修改作用域之外的值

     例如redux的reducer函数

setState是同步还是异步

setState除了修改值还会让组件重新渲染

setState接受两种方式:

 对象:

this.setState({ number:  1 })

  函数:第一个参数是上一个的state,第二个是props

this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

setState默认是同步,之所以异步是因为react本身的性能机制导致的,因为每次调用setState都会触发更新,异步操作是为了提高性能,将多个stateState调用合并成一个调用,减少重新渲染的次数。越过react的机制,setState就是同步的,比如在setTimeout中和原生事件中修改状态

react如何控制setState是同步还是异步的

在setState函数实现中会根据变量isBatchingUpdate的值判断是同步更新state还是放到队列中延时更新isBatchingUpdate默认是false表示setState默认是同步更新的

但是有一个函数batchUpdate它是用来将isBatchingUpdate的值变成true当react在调用事件处理函数之前会先调用batchUpdate这样在react的事件处理中state的更新就是异步的了

总结

由react控制的事件处理程序以及生命周期中调用setState不会同步更新state

由react控制之外的事件中调用setState是同步的比如原生js绑定事件setTimeout等

练习题:

1.异步 + 多次调用,异步的时候多次调用setState会合并,回调函数是等到state赋值完成之会才开始执行

state = { count: 0 }
add = () => {
  this.setState({ count: this.state.count + 1 }, () => {
    console.log(this.state.count) // 2
  })
  this.setState({ count: this.state.count + 2 }, () => {
    console.log(this.state.count) // 2
  })
  console.log(this.state.count) // 0
}

2.异步,参数为函数形式的

state = { count: 0 }
add = () => {
  this.setState((prevCount) => ({ count: prevCount.count + 1 }), () => {
    console.log(this.state.count)//3 
  })
  this.setState((prevCount) => ({ count: prevCount.count + 2 }), () => {
    console.log(this.state.count)//3 
  })
  console.log(this.state.count)//0
}

3.同步+异步。 同步中回调函数是同步执行的

state = { count: 0 }
add = () => {
  setTimeout(() => {
    this.setState((prevCount) => ({ count: prevCount.count + 1 }), () => {
      console.log(this.state.count) //3
    })
    this.setState((prevCount) => ({ count: prevCount.count + 1 }), () => {
      console.log(this.state.count) // 4
    })
    this.setState((prevCount) => ({ count: prevCount.count + 1 }), () => {
      console.log(this.state.count) // 5
    })
  }, 0);
  this.setState((prevCount) => ({ count: prevCount.count + 2 }))
}

总结:

同步中的回调是同步执行的

异步中的回调是等state的队列执行完,才会执行的

合成事件:

为了解决跨浏览器的兼容性问题,React会将浏览器的原生事件封装成合成事件。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为一致,重要的是,React并没有将事件直接绑定在子元素上,而是将所有的事件都发送到顶层的document上进行处理,这样react在更新dom的时候就不需要考虑如何去处理绑定在dom上的事件,最终达到优化性能的目的。

生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载时)
  • Updating(更新时)
  • Unmounting(卸载时)

 

挂载阶段

constructor、getDerivedStateFromProps、render、componentDidMount

更新阶段

getDerivedStateFromProps、shouldComponentUpdate、getSnapShotBeforeUpdate、render、componentDidUpdate

卸载

componentWillUnmount

React-router

两种实现方式

hashRouter:利用hash实现路由切换

原理:当路径变化会触发一个hashchange的事件,保证ui和url同步

browserRouter:利用h5的api实现路由切换,

Window.history 提供了操作浏览器会话历史的接口,Window.location 提供了路径信息

pushState和replaceState方法分别是给浏览器添加条目和修改条目,不会触发popstate事件,popstate只会在浏览器的某些行为上会被触发,比如点击前进后退按钮

需要加强pushState的方法让它能保持原有的功能的同时能够触发事件

9.vue和react的区别

共同点:

  • 虚拟 DOM。映射真实 DOM,通过新旧 DOM 的 diff 对比,更好的跟踪渲染页面。
  • 组件化。将应用拆分成一个个功能明确的模块,每个模块之间可以通过合适的方式互相联系。
  • 构建工具。都有自己的构建工具,通过 Webpack + Babel 去搭建脚手架。
  • Chrome 开发工具。两者都有很好的 Chrome 扩展去帮助查找 Bug。
  • 配套框架。Vue 有 Vue-routerVuex,而 React 有 React-routerReact-Redux

不同点:

  • 模板 VS JSX。Vue 推荐编写近似常规 HTML 的模板进行渲染,而 React 推荐 JSX 的书写方式。
  • 监听数据变化的不同。Vue 使用的是可变数据,而 React 更强调数据的不可变。在 Vue 中通过 v-model 绑定的数据,用户改变输入值后对应的值也相应改变。而 React 需要通过 setState 进行设置变化。
  • Diff 不同。Vue 通过双向链表实现边对比边更新 DOM,而 React 通过 Diff 队列保存需要更新的 DOM,得到 patch 树,再统一批量更新 DOM。

组件数据交互

Vue prop +自定义事件

React props + 回调

Fiber:

Fiber 会使原本同步的渲染过程变成异步的

1.raf

requestAnimationFrame:告诉浏览器你需要执行一个动画,并且要求浏览器下次重绘前会调用它的回调函数来更新动画

2. ric

requestIdleCallback:空闲的时候调它的回调,如果给了超时时间,就要立马执行

3.单链表

fiber采用单链表的存储数据,因为它可以很方便的中断和恢复

4.fiber的历史

fiber之前,react会递归对比虚拟dom,找出需要变动的节点,然后同步更新它们,这个过程称之为协调,(无法中断,因为中断后想恢复就很难了)

在协调阶段,react一直会占用浏览器的资源,导致会用触发事件的事件一直到不到响应,用户就会认为卡顿

Fiber之前的数据结构是一棵树,父节点的children指向了子节点,但是只有这一个指针是不能实现中断恢复的。

fiber是什么?

fiber是将任务拆成一个个很小的任务,让他们在浏览器空闲的时候去执行它们,每次执行完一个任务都检查还剩多长时间 如果没有剩余时间就交出控制权给到浏览器

fiber的数据结构在之前的基础上加上了指向父节点和兄弟节点的指针,Fiber这个结构外形看着还是棵树,但是没有了指向所有子元素的指针,父节点只指向第一个子节点,然后子节点有指向其他子节点的指针,这其实是个链表。

  1. child: 父节点指向第一个子元素的指针。
  2. sibling:从第一个子元素往后,指向下一个兄弟元素。
  3. return:所有子元素都有的指向父元素的指针。

受控和非受控组件

受控组件表单的数据是由react组件来管理的 (推荐)

非受控组件表单数据是由dom节点来处理的 ,使用ref获取表单的数据

react为什么要加key,并且key最好不要用index?

假设现在有这样一段代码:

const users = [{ username: "bob" }, { username: "sue" }];

users.map((u, i) => <div key={i}>{u.username}</div>);

它会渲染出这个 DOM 树:

<div key="1">bob</div>
<div key="2">sue</div>

然后用户做了某个操作,users 被 unshift 另一个对象,变成:

const users = [
  { username: "new-guy" },
  { username: "bob" },
  { username: "sue" },
];

DOM 树就会变成这样,注意 key 的变化:

<div key="1">new-guy</div>
<div key="2">bob</div>
<div key="3">sue</div>

DOM 树的前后对比是这样的

<div key="1">bob</div>   |  <div key="1">new-guy</div>
<div key="2">sue</div>   |  <div key="2">bob</div>
                         |  <div key="3">sue</div>

我们人类看得出来前后的变化只是在开头加了一个 new-guy 而已。

但是由于 React 使用 key 值来识别变化,所以 React 认为的变化是:

  1. bob -> new-guy
  2. sue -> bob
  3. 添加 sue

非常消耗性能 ?

但是如果我们一开始就给它指定一个合适的 key,比如用 name:

users.map((u, i) => <div key={u.username}>{u.username}</div>);

React 认为的变化就变成:

                         |  <div key="1">new-guy</div>
<div key="1">bob</div>   |  <div key="2">bob</div>
<div key="2">sue</div>   |  <div key="3">sue</div>

react怎么实现强制刷新组件

1.通过this.forceUpdate()

2.通过key

【key 的作用】
   react 中的 key 属性,它是一个特殊的属性,它的出现不是给开发者用的,而是给 react 自己用的。react 利用 key 来识别组件,它是一种身份标识。每个 key 对应一个组件,相同的 key ,react 认为是同一个组件,这样后续相同 key 对应的组件都不会被创建,而是会被传入新的数据,更新组件。有了 key 属性后,就可以与组件建立了一种对应关系,react 根据 key 来决定是销毁重新创建组件还是更新组件。

key 相同,若组件属性有所变化,则 react 只更新组件对应的属性;没有变化则不更新。
key 值不同,则 react 先销毁该组件,然后重新创建该组件。

React 废弃了哪些生命周期?为什么?

1.react16.8采用的fiber会根据优先级的高低执行任务,高优先级任务的出现而打断现有任务导致它们会被执行多次。另外的一个原因则是,React想约束开发者,因为有更好的生命周期可以替代它们比如componentWillReceiveProps 可以用getDerivedStateFromProps

React的异步为什么不放在constructor里 提前调用 而要放在componentDidMount

如果认为在constructor,componentWillMount里发起请求能提早获得结果,这种想法其实是错误的,通常componentWillMount比componentDidMount早不了多少微秒,网络上任何一点延迟,这一点差异都可忽略不计。

react的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()

上面这些方法的调用是有次序的,由左到右依次调用。

  • constructor:可以放,但从设计上而言不推荐。constructor 主要用于初始化 state 与函数绑定,并不承载业务逻辑。
  • componentWillMount:已被标记废弃,在新的异步渲染架构下会触发多次渲染,容易引发 Bug,不利于未来 React 升级后的代码维护。
  • componentDidMount方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用setState方法,会触发重新渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。

组件共享数据方式

1.通过props

缺点:1.是当逐层向下传递的层数变多时定义和使用的地方相隔有点远

           2.父子组件耦合(子组件需要知道怎么使用传进来的参数)

<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

2.组件组合:

将 Avatar 组件自身传递下去,因而中间组件无需知道 user 或者 avatarSize 等 props:

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}

优点:1.子组件和直接关联的父组件解耦

           2.减少了在你的应用中要传递的 props 数量

缺点:这种将逻辑提升到组件树的更高层次来处理,会使得这些高层组件变得更复杂

3.Context

提供了无需显示的逐层给每层组件添加props,就可以在组件树中共享这些值

设计的目的是为了共享那些对于在组件树中充当‘全局’的变量

React有哪些优化性能的手段

1.React.memo包裹子组件

    react默认会让所有的子组件更新,当父组件的属性变化,但是这个属性并没有传给子组件,那么可以避免重新渲染子组件

    被memo包裹的子组件只有两种情况下才会渲染

    1.当props变化的时候(浅对比)

    2.组件里面用了全局的状态比如redux变化的时候

    并不推荐所有子组件都被memo包裹,只有当子组件非常消耗性能的时候才推荐用memo,因为memo自身也需要进行新老数据进行浅对比

2.循环使用key 用唯一值 而不是index

3.使用useCallback、useMemo

4.使用懒加载的方式加载组件

5.在构造函数中bind this或者函数用箭头函数的方式 让函数中的this指向当前实例

react遇到的坑?

 JS关键字的冲突:给组件类名不是用class而是className

// for改成htmlFor,class改成className
<label htmlFor="input-name" className="label">
  用户名 <input id="username" />
</label>

JSX数据类型:用双引号表示传的是字符串 {}表示传的是原本的类型

// correct
<Demo flag={true} age={18}/>
// error
<Demo flag="true" age="18"/>

  自定义组件的名称首字母要大写

// 原生html组件
<input />

// 自定义组件
<Input />

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值