大厂面经---详解react hooks面试题【React系列02】
共 9788字,需浏览 20分钟
·
2021-03-31 11:32
关注公众号 前端人,回复“加群”
添加无广告优质学习群
1. 简单介绍下什么是hooks,hooks产生的背景?hooks的优点?
hooks是针对在使用react时存在以下问题而产生的:
组件之间复用状态逻辑很难,在hooks之前,实现组件复用,一般采用高阶组件和 Render Props,它们本质是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来嵌套地域。 组件逻辑变得越来越复杂,尤其是生命周期函数中常常包含一些不相关的逻辑,完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。 复杂的class组件,使用class组件,需要理解 JavaScript 中 this 的工作方式,不能忘记绑定事件处理器等操作,代码复杂且冗余。除此之外,class组件也会让一些react优化措施失效。
针对上面提到的问题,react团队研发了hooks,它主要有两方面作用:
用于在函数组件中引入状态管理和生命周期方法 取代高阶组件和render props来实现抽象和可重用性
优点也很明显:
避免在被广泛使用的函数组件在后期迭代过程中,需要承担一些副作用,而必须重构成类组件,它帮助函数组件引入状态管理和生命周期方法。
Hooks 出现之后,我们将复用逻辑提取到组件顶层,而不是强行提升到父组件中。这样就能够避免 HOC 和 Render Props 带来的「嵌套地域」
避免上面陈述的class组件带来的那些问题
2. 知道hoc和render props吗,它们有什么作用?有什么弊端?
Render Props 组件和高阶组件主要用来实现抽象和可重用性。
弊端就是高阶组件和 Render Props 本质上都是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来的「嵌套地域」。
由于所有抽象逻辑都被其他 React 组件所隐藏,应用变成了一棵没有可读性的组件树。而那些可见的组件也很难在浏览器的 DOM 中进行跟踪。
2.1 Render Props
什么是Render Props
render props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力。他把组件可以动态渲染的地方暴露给外部,你不用再关注组件的内部实现,只要把数据通过函数传出去就好。
使用场景:
通用业务逻辑的抽取 当两个平级组件之间需要单向依赖的时候,比如两个同级组件A、B,A组件需要跟随B组件的内部状态来改变自己的内部状态,我们就说A依赖B;或者B依赖A
2.2 Hoc
hoc是 React 中用于重用组件逻辑的高级技术,它是一个函数,能够接受一个组件并返回一个新的组件。
实现高阶组件的两种方式:
属性代理。高阶组件通过包裹的React组件来操作props 反向继承。高阶组件继承于被包裹的React组件
2.2.1属性代理
a. 什么是属性代理
属性代理组件继承自React.Component,通过传递给被包装的组件props得名
// 属性代理,把高阶组件接收到的属性传递给传进来的组件
function HOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
b. 属性代理的用途
更改 props,可以对传递的包裹组件的WrappedComponent的props进行控制 通过 refs 获取组件实例
/*
可以通过 ref 获取关键词 this(WrappedComponent 的实例)
当 WrappedComponent 被渲染后,ref 上的回调函数 proc 将会执行,此时就有了这个 WrappedComponent 的实例的引用
*/
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return <WrappedComponent {...props}/>
}
}
}
把 WrappedComponent 与其它 elements 包装在一起
2.1.2 反向继承
反向继承是继承自传递过来的组件
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
反向继承允许高阶组件通过 this 关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期(component lifecycle)钩子,以及渲染方法(render),所以我们主要用它来做渲染劫持,比如在渲染方法中读取或更改 React Elements tree,或者有条件的渲染等。
2.1.3 高阶组件相关的面试题
这怎么在高阶组件里面访问组件实例**
答案见上面你的项目中怎么使用的高阶组件**
a. 项目中经常存在在配置系统中配置开关/全局常量,然后在页面需要请求配置来做控制,如果在每个需要调用全局设置的地方都去请求一下接口,就会有一种不优雅的感觉,这个时候我就想到利用高阶组件抽象一下。
b. 项目开发过程中,经常也会遇到需要对当前页面的一些事件的默认执行做阻止,我们也可以写一个高阶组件等。hooks和hoc和render props有什么不同?
它们之间最大的不同在于,后两者仅仅是一种开发模式,而自定义的hooks是react提供的API模式,它既能更加自然的融入到react的渲染过程也更加符合react的函数编程理念。
介绍下常用的hooks?
useState(),状态钩子。为函数组建提供内部状态
// 我们实现一个简易版的useState
let memoizedStates = [ ] // 多个useState 时需要使用数组来存
let index = 0
function useState (initialState) {
memoizedStates[index] = memoizedStates[index] || initialState
let currentIndex = index;
function setState (newState) {
memoizedStates[currentIndex] = newState
render()
}
return [memoizedStates[index++], setState]
}
useContext(),共享钩子。该钩子的作用是,在组件之间共享状态。可以解决react逐层通过Porps传递数据,它接受React.createContext()的返回结果作为参数,使用useContext将不再需要Provider 和 Consumer。
useReducer(),Action 钩子。useReducer() 提供了状态管理,其基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。使用很像redux
useEffect(),副作用钩子。它接收两个参数, 第一个是进行的异步操作, 第二个是数组,用来给出Effect的依赖项
useRef(),获取组件的实例;渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)
useRef传入一个参数initValue,并创建一个对象{ current: initValue }给函数组件使用,在整个生命周期中该对象保持不变。
useMemo和useCallback:可缓存函数的引用或值,useMemo用在计算值的缓存,注意不用滥用。经常用在下面两种场景(要保持引用相等;对于组件内部用到的 object、array、函数等,如果用在了其他 Hook 的依赖数组中,或者作为 props 传递给了下游组件,应该使用 useMemo/useCallback)
useLayoutEffect:会在所有的 DOM 变更之后同步调用 effect,可以使用它来读取 DOM 布局并同步触发重渲染
描述下hooks下怎么模拟生命周期函数,模拟的生命周期和class中的生命周期有什么区别吗?
// componentDidMount,必须加[],不然会默认每次渲染都执行
useEffect(()=>{
}, [])
// componentDidUpdate
useEffect(()=>{
document.title = `You clicked ${count} times`;
return()=>{
// 以及 componentWillUnmount 执行的内容
}
}, [count])
// shouldComponentUpdate, 只有 Parent 组件中的 count state 更新了,Child 才会重新渲染,否则不会。
function Parent() {
const [count,setCount] = useState(0);
const child = useMemo(()=> <Child count={count} />, [count]);
return <>{count}</>
}
function Child(props) {
return <div>Count:{props.count}</div>
}
这里有一个点需要注意,就是默认的useEffect(不带[])中return的清理函数,它和componentWillUnmount有本质区别的,默认情况下return,在每次useEffect执行前都会执行,并不是只有组件卸载的时候执行。
useEffect在副作用结束之后,会延迟一段时间执行,并非同步执行,和compontDidMount有本质区别。遇到dom操作,最好使用useLayoutEffect。
hooks中的坑,以及为什么?
不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
使用useState时候,使用push,pop,splice等直接更改数组对象的坑,demo中使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。(这个的原因是push,pop,splice是直接修改原数组,react会认为state并没有发生变化,无法更新) 这里的坑很多的,经常出现的就是每次修改数组的时候:
const [firstData, setFirstData]: any = useState([]);
const handleFirstAdd = () => {
// let temp = firstData
// 不要这么写,直接修改原数组相当于没有更新
let temp = [...firstData];
// 必须这么写,多层数组也要这么写
temp.push({
value: "",
});
setFirstData(temp);
};
function Indicatorfilter() {
let [num, setNums] = useState([0, 1, 2, 3]);
const test = () => {
// 这里坑是直接采用push去更新num,setNums(num)是无法更新num的,必须使用num = [...num ,1]
num.push(1);
// num = [...num ,1]
setNums(num);
};
return (
<div className="filter">
<div onClick={test}>测试</div>
<div>
{num.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
}
class Indicatorfilter extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
nums: [1, 2, 3],
};
this.test = this.test.bind(this);
}
test() {
// class采用同样的方式是没有问题的
this.state.nums.push(1);
this.setState({
nums: this.state.nums,
});
}
render() {
let { nums } = this.state;
return (
<div>
<div onClick={this.test}>测试</div>
<div>
{nums.map((item: any, index: number) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
}
}
useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect
TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail的columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
}
正确的做法是通过useEffect改变这个值
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
useEffect(() =>{setTabColumn(columns)},[columns])
}
原文地址:https://blog.csdn.net/kellywong/article/details/106430977
最后
关注公众号,置顶公众号
,鬼哥
,一起前端进阶
公众号里回复关键词 资料包
领取我整理的进阶资料包公众号里回复关键词 加群
,加入前端进阶群文章点个 在看
,支持一下把!