web面试题记录之react
Posted jiaojsun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web面试题记录之react相关的知识,希望对你有一定的参考价值。
为什么使用hooks
class 在组件之间复用状态逻辑很难,由高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。| Hook 从组件中提取状态逻辑, 使得这些逻辑可以单独复用
【拆分】class 组件不好理解, 每个生命周期常常包含一些不相关的逻辑【例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,】| Hook 将组件中相互关联的部分拆分成更小的函数
class组件需要理解各种this的指向,class 不能很好的压缩、
react hooks带来了什么便利
在没有 hooks 之前,我们使用函数定义的组件中,不能使用 React 的 state、各种生命周期钩子类组件的特性。在 React 16.8 之后,推出了新功能: Hooks,通过 hooks 我们可以再函数定义的组件中使用类组件的特性。
好处:
跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
相比而言,类组件的实现更为复杂
不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
时刻需要关注this的指向问题;
代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
状态与 UI 隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。
注意:
避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
不能在useEffect中使用useState,React 会报错提示;
类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存
Hooks的缺点
学习成本 2.初学者在useEffect里面会存在死循环的问题
3.自定义 hook 也是 hook,只能在函数组件的顶层使用,不能在 if 或 for 循环中使用;
diff算法:
目的就是对比两次渲染的结果,找到可复用的部分,剩下的进行删除和新增就可以了
虚拟DOM
虚拟DOM表示真实DOM的JS对象,如何做到快速找到两个虚拟DOM之间存在的差异,来可以最小化的更新视图,就需要vue的diff算法
干前端的都知道DOM操作是性能杀手,因为操作DOM会引起页面的回流或者重绘。那么为什么现在的框架都使用虚拟DOM呢?因为使用虚拟DOM可以提高代码的性能下限,并极大的优化大量操作DOM时产生的性能损耗【因为 VM 并不是真实的操作 DOM,通过 diff 算法可以避免一些不变要的 DOM 操作,从而提高了性能。】
69. react的虚拟DOM是怎么实现的
React 是把真实的 DOM 树转换为 JS 对象树,也就是 Virtual DOM。每次数据更新后,重新计算 VM,并和上一次生成的 VM 树进行对比,对发生变化的部分进行批量更新。除了性能之外,VM 的实现最大的好处在于和其他平台的集成。
React的diff算法:
diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新
具体的流程是这样的:
真实 DOM 与虚拟 DOM 之间存在一个映射关系。这个映射关系依靠初始化时的 JSX 建立完成;
当虚拟 DOM 发生变化后,就会根据差距计算生成 patch,这个 patch 是一个结构化的数据,内容包含了增加、更新、移除等;
最后再根据 patch 去更新真实的 DOM,反馈到用户的界面上。
更新时机:更新发生在setState、Hooks 调用等操作以后。
遍历算法:深度优先遍历算法。因为广度优先遍历可能会导致组件的生命周期时序错乱,而深度优先遍历算法就可以解决这个问题。
优化策略:策略一:忽略DOM树跨层级操作场景,提升比对效率。策略二:如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构【React.memo 可以提高性能的原因。】策略三:同一层级的子节点,可以通过标记 key 的方式进行列表对比
React 16 引入Fiber 设计:使得整个更新过程可以随时暂停恢复
性能优化
useCallback + memo 避免组件不必要重的重复渲染
useMemo 避免组件在每次渲染时都进行高开销的计算
react数据通信
类组件和函数式组件的区别
语法上:函数组件是一个纯函数, 它接收一个props对象返回一个react元素
而类组件需要去继承React.Component并且创建render函数返回react元素
状态管理:因为函数组件是一个纯函数,你不能在组件中使用setState(),这也是为什么把函数组件称作为无状态组件。
生命周期钩子:你不能在函数组件中使用生命周期钩子,原因和不能使用state一样,所有的生命周期钩子都来自于继承的React.Component中。
高阶组件HOC
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
高阶组件的参数为一个组件返回一个新的组件
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件
受控组件
非受控组件,即组件的状态不受React控制的组件,例如下边这个
import React, Component from 'react';
import ReactDOM from 'react-dom';
class Demo1 extends Component
render()
return (
<input />
)
ReactDOM.render(<Demo1/>, document.getElementById('content'))
在这个最简单的输入框组件里,我们并没有干涉input中的value展示,即用户输入的内容都会展示在上面
受控组件就是组件的状态受React控制。上面提到过,我们把input的value属性和state结合在一起,再绑定onChange事件,实时更新value值就行了。
class Demo1 extends Component
constructor(props)
super(props);
this.state =
value: props.value
handleChange(e)
this.setState(
value: e.target.value
)
render()
return (
<input value=this.state.value onChange=e => this.handleChange(e)/>
)
JSX
JSX即javascript XML。一种在React组件内部构建标签的类XML语法。
class MyComponent extends React.Component
render()
let props = this.props;
return (
<div className="my-component">
<a href=props.url>props.name</a>
</div>
);
优点:
允许使用熟悉的语法来定义 html 元素树;
提供更加语义化且移动的标签;
程序结构更容易被直观化;
抽象了 React Element 的创建过程;
可以随时掌控 HTML 标签以及生成这些标签的代码;
是原生的 JavaScript。
React是啥
React是一个简单的javascript UI库,用于构建高效、快速的用户界面。
它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。
它使用虚拟DOM来有效地操作DOM。
它遵循从高阶组件到低阶组件的单向数据流。
常见的hook
状态钩子 (useState): 用于定义组件的 State,类似类定义中 this.state 的功能
生命周期钩子 (useEffect): 类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里可以看做componentDidMount、componentDidUpdate和componentWillUnmount的结合。
useContext: 获取 context 对象,不需要死板的一层一层的传递数据
useCallback: 缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染,具有性能优化的效果;
useMemo: 用于缓存传入的 props,避免依赖的组件每次都重新渲染;
useRef: 获取组件的真实节点【常用一点】或者其他的值,比较灵活;[class里面如果把 refs 放到 React 组件中,那么我们获得的就是组件的实例]
useReducer 是useState的复杂版本,和redux类似
react-router里的<Link>和<a>标签的区别
对比 标签, Link 避免了不必要的重新渲染。【Link 的 “跳转” 行为只会触发相匹配的对应的页面内容更新,而不会刷新整个页面】
而 a 标签就是普通的超链接了,用于从当前页面跳转到href指向的另一个页面(非锚点情况)。
redux
核心描述
单一数据源:整个应用的全局 state 被存储在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事情的普通对象。
使用纯函数来执行修改:为了描述 action 如何改变 state tree,你需要编写纯的 reducers。
知识拓展
什么时候应该使用 redux:
在应用的大量地方,都存在大量的状态
应用状态会随着时间的推移而频繁更新
更新该状态的逻辑可能很复杂
中型和大型代码量的应用,很多人协同开发
reducer 是一个函数,接收当前的 state 和一个 action 对象,必要时决定如何更新状态,并返回新状态。reducer 必须符合以下规则:
仅使用 state 和 action 参数计算新的状态值
禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做不可变更新
禁止任何异步逻辑、依赖随机值或导致其他副作用代码
reducer 遵守上述规则的原因:
redux 的目标之一是使代码可预测。当函数的输出仅根据输入参数计算时,更容易理解该代码的工作原理并对其进行测试
如果一个函数依赖于自身之外的变量,或者随机行为,你永远不知道运行它时会发生什么
如果一个函数 mutate 了其他对象,比如它的参数,这可能会意外地改变应用程序的工作方式。这可能是错误的常见来源
不可变更新(Immutability),不能在 Redux 中更改 state 的原因:
会导致bug,例如 UI 未正确更新以显示最新值
更难理解状态更新的原因和方式
编写测试变的困难
打破了正确使用“时间旅行调试”的能力
违背了 Redux 的预期精神和使用模式
自定义hooks和普通函数的区别
官方提供的Hooks只应该在React函数组件/自定义Hooks内调用,而不应该在普通函数调用。
自定义Hooks能够调用诸如useState、useRef等,普通函数则不能。由此可以通过内置的Hooks获得Fiber的访问方式,可以实现在组件级别存储数据的方案等。
自定义Hooks需要以use开头,普通函数则没有这个限制。使用use开头并不是一个语法或者一个强制性的方案,更像是一个约定,就像是GET请求约定语义不携带Body一样,使用use开头的目的就是让React识别出来这是个Hooks,从而检查这些规则约束,通常也会使用ESlint配合eslint-plugin-react-hooks检查这些规则,从而提前避免错误的使用。
如果我们使用Hooks的话,实际上由于可以调用useState、useRef等Hooks,从而获取了对于这个Fiber的访问方法,那么也就相当于我们可以将状态或者说数据存放于当前节点当中,而不是类似于普通函数在全局中共享。当然如果需要全局共享状态的话,状态管理方案是更好的选择,而不是全局变量。
https://mp.weixin.qq.com/s/GQQlE5WD9xzExixZhjMKTw
先说结论,首先,同步和异步主要取决于它被调用的环境。
如果 setState 在 React 能够控制的范围被调用,它就是异步的。
比如合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。
如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。
比如原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。
为什么会这样呢?
其实,我们看到的所谓的 “异步”,是开启了 “批量更新” 模式的。
批量更新模式可以减少真实 DOM 渲染的次数,所以只要是 React 能够控制的范围,出于性能因素考虑,一定是批量更新模式。批量更新会先合并状态,再一次性做 DOM 更新。
react18之后。
setState都会表现为异步(即批处理)。
react的性能优化
8.4JSX本质是什么
JSX 是一个 JavaScript 的语法扩展,它看起来像是一种模板语言,但它具有 JavaScript 的全部功能。
React.JSX转换成真实DOM过程
JSX通过babel最终转化成React.createElement这种形式。返回虚拟DOM(vnode)
在转化过程中,babel在编译时会判断 JSX 中组件的首字母:
当首字母为小写时,其被认定为原生 DOM 标签,createElement 的第一个变量被编译为字符串
当首字母为大写时,其被认定为自定义组件,createElement 的第一个变量被编译为对象
虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:
ReactDOM.render(<App />, document.getElementById("root"));
14.1关于react hooks
14.2函数组件的特点
14.4useEffect生命周期模拟
DIdMount || WillUnMount
DidUpdate
fetch...直接放在Fetch组件内部,而不是用useEffect包裹起来也是可以的
但是!
理论上可以,useeffect本身是一个副作用,直接在函数里面进行这样的一个副作用,问题是很大的。因为组件随时都有可能去更新,比如副组件的更新会导致子租件更新,或者内部一些状态的更新导致组件的变化,或者props的变化等等。每一次更新,这些东西(fetch里面)都是要再次执行的,这样肯定不行啊。
所以上面console.log打印的次数是7次,放在useEffect里面只会打印2次。
这个是为什么呢?
useEffect(()=>)
这样写执行两次:在组件每一次更新的时候都会去执行,第一次更新是初始的渲染(相当于didmount),第二次是setResult更新完了之后渲染(相当于didUpdate)
useEffect(()=>,[])
这样写只会执行一次,后面不会再执行(相当于didmount)
总结:
class 组件 | Hooks 组件 |
constructor | useState |
getDerivedStateFromProps | useEffect 手动对比 props, 配合 useState 里面 update 函数 |
shouldComponentUpdate | React.memo |
render | 函数本身 |
componentDidMount | useEffect 第二个参数为[] |
componentDidUpdate | useEffect 配合useRef |
componentWillUnmount | useEffect 里面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |
14.12使用规范
ps: 不能在一起可能被打断的程序后面,如上面所示
14 13 为何Hooks要依赖于调用顺序
如何不按照这样的规则可能发生一些错乱
react-router的理解
react-router可以实现无刷新的条件下:改变浏览器地址栏中的URL地址,切换显示不同的页面,
两种模式:
hash 模式:在url后面加上#,如http://127.0.0.1:5500/home/#/page1
history 模式:允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录
这两种模式对应的组件为:BrowserRouter、HashRouter: 作为最顶层组件包裹其他组
react-router的使用
Route用于路径的匹配,然后进行组件的渲染,
path 属性:用于设置匹配到的路径
component 属性:设置匹配到路径后,渲染的组件
render 属性:设置匹配到路径后,渲染的内容
exact 属性:开启精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
import BrowserRouter as Router, Route from "react-router-dom";
export default function App()
return (
<Router>
<main>
<nav>
<ul>
<li>
< a href="/">Home</ a>
</li>
<li>
< a href="/about">About</ a>
</li>
<li>
< a href="/contact">Contact</ a>
</li>
</ul>
</nav>
<Route path="/" render=() => <h1>Welcome!</h1> />
</main>
</Router>
);
Link、NavLink:
路径的跳转是使用Link组件,最终会被渲染成a元素.其中属性to代替a标题的href属性,
NavLink是在Link基础之上增加了一些样式属性,例如组件被选中时,发生样式变化,则可以设置NavLink的一下属性:
activeStyle:活跃时(匹配时)的样式
activeClassName:活跃时添加的class
<NavLink to="/" exact activeStyle=color: "red">首页</NavLink>
<NavLink to="/about" activeStyle=color: "red">关于</NavLink>
<NavLink to="/profile" activeStyle=color: "red">我的</NavLink>
redirect
用于路由的重定向
switch
swich组件的作用适用于当匹配到第一个组件的时候,后面的组件就不应该继续匹配
<Switch>
<Route exact path="/" component=Home />
<Route path="/about" component=About />
<Route path="/profile" component=Profile />
<Route path="/:userid" component=User />
<Route component=NoMatch />
</Switch>
如果不使用switch组件进行包裹,相同 path 的就会被匹配到,然后一起展示。
除了一些路由相关的组件之外,react-router还提供一些hooks,如下:
useHistory
useParams
useLocation
react-router的参数传递
这些路由传递参数主要分成了三种形式:
动态路由的方式
search传递参数
to传入对象
动态路由
动态路由的概念指的是路由中的路径并不会固定
例如将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route
<NavLink to="/detail/abc123">详情</NavLink>
<Switch>
... 其他Route
<Route path="/detail/:id" component=Detail/>
<Route component=NoMatch />
</Switch>
获取参数方式如下:
console.log(props.match.params.xxx)
search传递参数
在跳转的路径中添加了一些query参数;
<NavLink to="/detail2?name=why&age=18">详情2</NavLink>
<Switch>
<Route path="/detail2" component=Detail2/>
</Switch>
获取形式如下:
console.log(props.location.search)
to传入对象
传递方式如下:
<NavLink to=
pathname: "/detail2",
query: name: "kobe", age: 30,
state: height: 1.98, address: "洛杉矶",
search: "?apikey=123"
>
详情2
</NavLink>
获取参数的形式如下
console.log(props.location)
react-router的原理
不会导致浏览器向服务器发送请求,就不会刷新页面:
原理【hash】:hash模式路由就是利用 hashchange 事件监听 URL 的变化,从而进行 DOM 操作来模拟页面跳转
原理【router】:通过props传进来的path与context传进来的pathname进行匹配,然后决定是否执行渲染组件
React 18的新特性
1.批处理: react18以前批处理只限于 React 原生事件内部的更新。React 18批处理支持处理的操作范围扩大了:Promise,setTimeout,native event handlers 等这些非 React 原生的事件内部的更新也会得到合并:
// Before: only React events were batched.
setTimeout(() =>
setCount((c) => c + 1);
setFlag((f) => !f);
// React will render twice, once for each state update (no batching)
, 1000);
// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() =>
setCount((c) => c + 1);
setFlag((f) => !f);
// React will only re-render once at the end (that's batching!)
, 1000);
2.Transitions
Transitions 是 React 中一个用于区分高优更新和非高优更新的新概念。
starTransition:用于标记非紧急的更新,用 starTransition 包裹起来就是告诉 React,这部分代码渲染的优先级不高,可以优先处理其它更重要的渲染。
import startTransition from "react";
// Urgent
setSliderValue(input);
// Mark any state updates inside as transitions
startTransition(() =>
// Transition: Show the results, non-urgent
setGraphValue(input);
);
useTimeout 实现
function useTimeout(callback, delay)
const memorizeCallback = useRef();
useEffect(() =>
memorizeCallback.current = callback;
, [callback]);
useEffect(() =>
if (delay !== null)
const timer = setTimeout(() =>
memorizeCallback.current();
, delay);
return () =>
clearTimeout(timer);
;
, [delay]);
;
如何使用
// callback 回调函数, delay 延迟时间
useTimeout(callback, delay);
react hooks中如何捕获异常
-在React项目中,因为事件处理程序总是需要写 try/catch,不胜其烦。
-虽然可以丢给window.onerror或者 window.addEventListener("error")去处理,但是对错误细节的捕获以及错误的补偿是极其不友好的。
-实现: 其基本原理就是利用 useMemo和之前封装的observerHandler
export function useCatch<T extends (...args: any[]) => any>(callback: T, deps: DependencyList, options: CatchOptions =DEFAULT_ERRPR_CATCH_OPTIONS): T
const opt = useMemo( ()=> getOptions(options), [options]);
const fn = useMemo((..._args: any[]) =>
const proxy = observerHandler(callback, undefined, function (error: Error)
commonErrorHandler(error, opt)
);
return proxy;
, [callback, deps, opt]) as T;
return fn;
redux使用的原则
单一数据源:整个应用的全局 state 被存储在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事情的普通对象。
使用纯函数来执行修改:为了描述 action 如何改变 state tree,你需要编写纯的 reducers。
react的事件代理机制
web前端面试题记录
记录了2017年5月下旬刚毕业时面试的经典面试题
布局方面
1. 响应式布局,左侧栏目固定,右侧内容随着屏幕宽度变化而变化(高频)
- flex布局
- position布局
- css3计算宽度
- float布局
flex布局
// html <div class="box"> <div class="left"></div> <div class="right"></div> </div> // css .box { display: flex; } .left { width: 200px; } .right { flex: 1; }
右侧div设置flex:1 自动填充满容器。
position布局
// html <div class="box"> <div class="left"></div> </div> // css .box { padding-left: 200px; width: 100%; position: relative; } .left { position: absolute; width: 200px; left: 0; }
用pading将要显示的右侧内容挤到右边,常用在图文列表
css3计算宽度
// html <div class="box"> <div class="left"></div> <div class="right"></div> </div> // css .left { float: left; width: 200px; } .right { float: left; width: calc(100% - 200px); }
通过css3的calc函数可以计算宽度来定义宽度
float布局(面试官想要的答案)
// html <div class="box"> <div class="left"></div> <div class="right"> <div class="inner"></div> </div> </div> // css .left { float: left; width: 200px; margin-right: -200px; } .right { float: left; width: 100%; } .inner { margin-left: 200px; }
根据float元素的margin特性布局,兼容性好。以上css都没有给出高度和颜色区分。
javascript方面
1. 闭包和作用域、this的理解
2. 原型链有关的问题
3. es6方面:let块级作用域、generator函数的应用
4. javascript中的setTimeout、promise异步的考查
5. jQuery中的设计模式
- 原型模式 : 整个jQuery库的构造就是一个原型继承的模式。
- 发布/订阅模式:事件监听模块为发布订阅模式
- 代理模式:jQuery中内置proxy方法便是代理模式
- 外观模式:post、get等方法是对ajax的包装
- 等等
6. jsonp的实现原理
js算法技巧方面
1. a[n] 数组中取值是 [1, n-1] ,也就是必然有重复数字,在时间复杂度和空间复杂度最小的情况下找出一个重复数字
博主也不懂复杂度,用正则写了个, a[n].toString().match(/(\\d+).*?\\1/)[1]
2. 两个单向链表的交点
博主懵逼,不懂数据结构不知啥叫链表交点。后来查了下就是两个链表成Y状,相交后必定后面的数据一样。这就不难了。
3. 给定一个 ram函数,该函数有50%几率返回0 和 50%几率返回1,根据这个ram函数写一个ran函数,ran函数有25%几率返回0 1 2 3。
博主脑子转不快,很慢很慢才理清楚这个简单的题,很尴尬。
http方面
1. 在浏览器输入一个网址到页面呈现,计算机做了哪些事情。
在一家公司的CTO问的,尴尬了,之前博主故意百度看了一遍这个问题,结果也是忘得一干二净。
在前端层面上就是 发送请求资源 - 建立连接 - 数据传输 - 解析数据
有很多大神写了完整过程: http://blog.csdn.net/xingxingba123/article/details/52743335 http://www.cnblogs.com/webhb/p/5615063.html
2. put和post请求的区别
一般情况我们用post请求来插入一条数据,用put请求更新一条数据。插入与更新的区别。。。
3. cookie和localStorage、sessionStorage的区别
cookie存储量小,存储数据小,跟随着http请求传输。
几次面试的总结,希望尽快掌握,下一次面试表现好一些
以上是关于web面试题记录之react的主要内容,如果未能解决你的问题,请参考以下文章