一文看清React面试中的重难点🌟

1,462 阅读18分钟

前言

这不是一篇React入门教程,所以不会对React中的基础内容做详细讲解,我始终认为看官网是学习React知识点最佳方式。本文只是对React中面试可能会问到的问题进行总结及讲解,希望大家对写得不好的地方多多提出意见,下面我们开始吧~

版本问题📒

现在出去面试都会问到hooks,你知道hooks是什么时候出来的吗,React还有什么新东西出来呢?

  1. React16.8之前还没有hooks,hooks为函数组件赋予了状态,16.8之前的函数组件是无状态组件,那个时候写状态组件只能用类组件,通过this.setState的方式去引起render的重新渲染。
  2. React16还出了新的协调引擎--React Fiber,它的主要目的是使虚拟DOM可以进行增量式渲染。

经典问题❓

  1. MVC和MVVM的区别
  • 二者都是设计模式,都是为了处理视图层、数据层以及数据视图之间的通信而诞生的

  • MVC

    • MVC = M(数据层)+ V (视图层)+ C(控制层)

      图片

      • Model和View中是观察者模式,当View中发生事件处理时,会通过Controller改变Model再改变View
      • 性能问题方面,在MVC中我们会大量操作DOM,频繁更新DOM,会阻塞浏览器渲染,影响用户体验
  • MVVM

    • MVVM = M(数据层)+ V (视图层)+ VM(控制层)

      • 和MVC的区别就在于,MVVM使用ViewModel替代了controller,Model和View没有直接的联系
      • ViewModel从Model中获取数据应用到View中,View中发生改变时,也会触发ViewModel
      • react准确来说是MVVM中VM部分的框架
  1. react怎么实现双向绑定
  • 通过state和onChange
  • vue是通过Object.defineProperty实现双向绑定
  1. 什么是单向数据流
  • 子组件只能通过props去接收父组件传递过来的数据
  1. react的虚拟DOM是什么,diff算法做了什么,Fiber又做了什么优化
  • 是一个模拟DOM节点的js对象,通过ReactDOM.render渲染到真实DOM树中
  • diff算法通过深度优先算法,对虚拟DOM树进行遍历,通过打补丁的方式,将变化更新到真实DOM中
  • 由于之前的diff算法一旦开始比对,是不能中断的,如果内容比较多的对比,可能会造成阻塞,所以Fiber采用了片段式更新,对任务划分优先级,通过Scheduler对任务执行时机进行调度,尽量减少对比过程中对浏览器的阻塞。
  • 此外,fiber树是一个特殊的链表结构,内部使用双缓冲模式对fiber树结构进行更新
  1. 类组件和函数组件有什么区别
  • 思想不同:类组件是面向对象的思想,函数组件是函数式编程
  • 类组件太重了,内部逻辑难以拆分和复用
  • 函数组件会捕获render内部的状态,函数组件会每次都重新创建一遍,可以实现状态的同步更新
  • 函数和react的理念更贴合,声明式编程

重难点

一、事件处理中传箭头函数和普通函数的区别

import React, { Component } from 'react'

export default class APP extends Component {
  a = 1

  handleClick () {
    console.log(this.a) // 报错:Cannot read properties of undefined (reading 'a')
  }

  render() {
    return (
      <div>
        <input/>
        
        <button onClick={() => {
          console.log(this.a) // 1
        }}>add1</button>
        
        <button onClick={this.handleClick}>add2</button>
      </div>
    )
  }
}

问题分析🔍

  • 箭头函数的this指向是周围环境决定的,传箭头函数的话,可以在箭头函数中获取类组件中的this

  • 普通函数的this指向是由调用的对象决定的,而这里的handleClick是由react的事件系统调用的,Es6中规定类中的函数,会默认开启局部的严格模式,也就是说this不指向window,而是undefind

解决方案🙋

普通函数通过bind去绑定this,将类组件的this手动绑定到函数中

补充知识点🍬

  • bind、call、apply改变this指向的区别?
  • 手写bind
const obj1 = {
  name: 'obj1',
  getName() {
    console.log(this.name)
  }
}

const obj2 = {
  name: 'obj2',
  getName() {
    console.log(this.name)
  }
}

obj1.getName() // obj1
obj1.getName.bind(obj2) // this指向obj2,但是不会自动执行函数,所以不会输出内容
obj1.getName.call(obj2) // 输出obj2,this指向obj2,会自动执行函数,接受参数是一个一个地传
obj1.getName.apply(obj2) // 输出obj2,this指向obj2,会自动执行函数,接受的参数必须是严格的数组

二、React中的事件绑定和原生事件绑定的区别🌟

问题分析🔍

React并没有将事件绑定到具体的元素身上,而是在document身上,通过合成事件实现

好处:占用内存小,不用担心去移除事件,完全支持原生事件机制暴露的内容

三、React列表中为什么需要设置Key值,并且key值为什么不能设置成index

问题分析🔍

  • 为什么需要key?

答:当页面中元素发生变化时,虚拟DOM会重新计算,根据key会比较方便地找出修改的地方,然后将修改的地方通过打补丁的方式同步给真实的DOM

  • 为什么key不能设置为index?

答:如果列表会发生重排、增删的情况下,将key设置成index会造成同一个元素修改前后key值不一样 (只作显示的列表可用index作为key)

/** 结论🍰:
  如果把index当作key的话,333这个元素在修改前后的key值发生了变化,
  虚拟DOM得出的结论是删除了值为333的li元素,实际上是删除了值为222的li标签
*/

// 修改前
<ul>
  <li key="111_0">111</li>
  <li key="222_1">222</li>
  <li key="333_2">333</li>
</ul>

// 修改后
<ul>
  <li key="0">111</li>
  <li key="1">333</li>
</ul>

四、React中条件渲染是创建/移除元素还是显示/隐藏元素

答:创建/移除,和vue的v-if以及v-show同理

五、React如何在页面中显示富文本

直白来讲就是在页面中直接解析代码片段,使用之前需确认该代码片段足够安全;

React DOM在渲染所有输出内容之前,默认会进行转义,所有内容都被转换成了字符串,可以有效地防止XSS(cross-site-scripting, 跨站脚本)攻击

// 使用dangerouslySetInnerHTML在页面中显示富文本内容
<span dangerouslySetInnerHTML={{
    __html: '<b>123456</b>' // 会直接显示加粗的123456,将字符串中的标签解析出来
  }}></span>

六、setState是同步还是异步?

问题分析🔍

setState在同步事件是异步的,因为每次state的改变都会引起重新渲染,为了提高性能,react会对组件中的setState操作进行合并,在事件循环机制中,宏任务执行完了之后才会去执行setState操作。官方解释

setState在异步事件中是同步更新状态。

这个是由react中批量更新的事务机制决定的,react会使用isBatchingUpdates变量去记录是否需要执行state队列中的内容,类似于锁的概念,当isBatchingUpdates的值变为false的时候,会开始执行state队列中的内容。在异步事件中,刚开始isBatchingUpdates是true,后面异步事件还未执行,isBatchingUpdates就变成了false,所以达到了同步的效果。

state = {
  count: 0
}

add = () => {
  this.setState({ count: this.state.count + 1 })
}

// 同步调用中,setState是异步的,所以最后this.state.count依然1
handleClick1 = () => {
  this.add()
  this.add()
  this.add()
}

// 异步调用中,setState是同步的,最后this.state.count是3
handleClick2 = () => {
  setTimeout(() => {
    this.add()
    this.add()
    this.add()
  }, 0)
}

解决方案🙋

setState(updater, [callback])

// 第一个参数传对象时,异步更新
setState({ count: 0 })

// 第一个参数传函数时,可以获取到最新的state和props值 => 也是把setState的异步改成同步的方法
setState((state, props) => {
  return { count: state.count + props.step }
})

// 第二个参数是可选的,可以拿到合并更新完的最新结果
setState({ count: 0 },() => {
  xxxx
})

七、state和props的区别和相同点

相同点

  • 都是js对象,都能引起render渲染
  • 都可以设置默认值

不同点

  • props是从父组件传过来的属性,子组件不可以对它进行修改(props是只读的)
  • 只有父组件主动更新props才能引起子组件内部的render渲染
  • state是组件的内部管理状态,外部无法获取
  • 只有通过setState更改state值才会引起render渲染

扩展🏄‍♀️

props的类属性类型验证(出于性能方面考虑,propTypes只在开发模式下进行检查)

// 使用组件
<App title="测试" show={false}/>

//写组件
import PropTypes from 'prop-types'

export default Class App extends React.component{
  // 写法1:在类的里面定义
  static propTypes = {
    title: PropTypes.string, // 限制title为字符串
    show: PropTypes.bool // 限制show为布尔值
  }
  
  // 设置默认值
  static defaultProps = {
    show: true
  }
  render() {
    const { title, show } = this.props
    return {show && <div>{title}</div>}
	}
}

// 写法2:在类的外面写
App.propTypes = {
  title: PropTypes.string, // 限制title为字符串
  show: PropTypes.bool // 限制show为布尔值
}

PS:函数式组件只能在组件外面写defaultProps和propTypes

八、通信

受控组件和非受控组件

广义的说法:通过React中的props属性完全控制的组件被称为受控组件,否则为非受控组件

在表单元素中,使React中的state作为唯一数据源,被React用state控制取值的表单输入元素就叫作受控组件

父子通信

父传子-->使用props传递数据

子传父--> 1. 传递方法;2. 通过ref获取子组件

非父子通信

  • 状态提升(子1/子2 --> 父,父 -->子,中间人的模式)
  • 发布订阅者模式
  • context状态树传参(生产者消费者模式)
const { Provider, Customer } = React.createContext()

// 生产者
class Page extends React.component{
  contructor(){
    this.state = {
      defaultValue: 'light'
		}
	}
  render{
    return(
      <Provider value={
        color: this.state.defaultValue,
        setColor: value => this.setState({ defaultValue: value })
      }>
        <Item1/>
        <Item2/>
      </Provider>
    )
  }
}

// 消费者1--改变值的组件
class Item1 extends React.component{
  return(
    <Customer>
      {value => <div onClick={() => value.setColor('dark')}>我来改变值</div>}
    </Provider>
  )
}

// 消费者2--接受值的组件
class Item2 extends React.component{
  return(
    <Customer>
      {value => <div>{value.color}</div>} // color为dark
    </Provider>
  )
}

扩展问题🏄‍♀️

➡观察者模式:监控一个对象的变化,一旦发生变化,就触发某种操作

🌰:老师观察学生,学生的状态原本是学习,一旦发现变为睡觉,就让学生去罚站

例子分析:

👀观察者:老师

⛰️被观察者:学生

📈模式:老师发现学生的状态变了,就做出某种响应

👩‍🏫观察者

  • 需要一个身份
  • 需要回调函数

👨‍🎓被观察者

  • 属性:自己的状态
  • 队列:记录谁在观察自己
  • 方法:设置自己的状态,当我发送改变的时候,可以改变自己的状态
  • 方法:添加观察者
  • 方法:删除观察者
// 观察者构造函数
class Observer {
  constructor(name, fn = () => {}){
    this.name = name
    this.fn = fn
  }
}

// 被观察者构造函数
class Subject {
  constructor (state) {
    this.state = state
    this.observers = [] // 存放观察者
  }
  
  setState(val) {
    this.setState = val
    
    // 执行观察者中的回调函数,并将被观察者的数据传给观察者
    this.obervers.forEach(item => {
      item.fn(val)
		})
  }
  
  // 增加观察者
  addObserver(obs) {
    // 避免重复添加观察者
    if (this.observers.findIndex(obs) < 0) {
      this.observers.push(obs)
    }
  }
  
  // 删除观察者
  deleteObserver(obs) {
    this.observers = this.observers.filter(item => item !== obs)
	}
}

// 创建观察者
const teacher = new Observer('老师', state => console.log('因为' + state + '被批评'))
const headermaster = new Observer('校长', state => console.log('因为' + state + '批评老师'))
 
// 创建被观察者
const xiaoming = new Subject('学习')

// 先注册观察者,再更改状态
xiaoming.addObserver(teacher)
xiaoming.addObserver(headermaster)
xiaoming.setState('睡觉') 

// 输出结果
因为睡觉被批评
因为睡觉批评老师

➡发布订阅模式:监听某个对象状态的变化,一旦发生变化,通过第三方告知监听者最新状态

比观察者多一个调度中心的概念

现实中的例子🌰

👨去书店买书📖,发现没有,给书店店员💁留了个电话,让店员书来了打电话给他


👨:观察者

📖 :被观察者

💁 :调度中心

// 观察者构造函数
class Observer{
  constructor(){
    this.message = {}
  }
  
  // 向消息队列中增加事件
  on(type, fn) {
    if (!this.message[type]) {
      this.message[type] = []
		}
    this.message[type].push(fn)
	}

	// 从消息队列中删除事件
	off(type, fn) {
    if (!fn) {
      delete this.message[type]
      return
		}
    if (this.message[type]) {
      this.message[type] = this.message[type].filter(f => f !== fn)
		}
	}

	// 触发消息队列
	tigger(type) {
    if (this.message[type]) {
      this.message[type].map(item => item())
		}
	}
}

// 创建调度中心
const clerk = new Observer()
const callBack1 = name => {
  console.log(name + '到货了!')
}
const callBack2 = name => {
  console.log(name + '只剩5本了!')
}

clerk.on('《活着》', callBack1)
clerk.on('《文城》', callBack1)
clerk.on('《文城》', callBack2)
clerk.off('《文城》', callBack1)
clerk.trigger('《活着》')
clerk.trigger('《文城》')

// 输出结果
《活着》到货了!
《文城》只剩5本了!

🌈区别分析

模式观察者发布订阅模式
优点角色明确1. 松耦合,发布者和订阅者无关联,靠调度中心联系
2. 灵活性较高,通常应用在异步编程中
缺点紧耦合当事件类型变多时,会增加维护成本
使用场景双向绑定react非父子组件通信

九、ref

React.createRef()

React.forwardRef(props, ref)

转发ref,可以直接获取子组件的DOM元素

扩展问题🏄‍♀️

**问1:**通过React.createRef()创建ref获取子组件的方式为什么不推荐使用?

答:ref过于暴露,直接通过ref获取子组件会对子组件有破坏性


**问2:**不能对函数组件使用ref,因为函数组件没有实例

答:在函数组件外部包裹forwardRef,将函数组件转换成能接受ref的组件,并且在父组件中使用useRef去创建ref

十、插槽

和vue中的slot类似,原理是使用this.props.children去渲染组件里放的内容

作用

  • 可以用于组件复用
  • 减少父子组件通信
// 基本用法
class Com extends React.component{
  render() {
    return (
      <div>{this.props.children}</div>
    )
	}
}

class CustomerCom extends React.component{
  render() {
    return (
      <Com>我是组件</Com>
    )
	}
}

// 最终页面显示:我是组件

十一、生命周期

16.2之后将diff算法更新到Fiber

16.8之后出现了react hooks,函数组件开始有生命周期

问:为什么componentWillmount、componentWillUpdate、componentWillReceiveProps会被废弃?

**答:**因为将diff算法更新到Fiber之后,低优先级的任务可能会被打断,也就是说可能会多次执行,而componentWillmount就是低优先级的任务,所以一般不建议使用componentWillmount,如果要初始化state值,推荐在constructor中写

👴老生命周期

componentWillmount

初始化,render之前最后一次修改state状态的机会

render

只能访问state和props,不允许修改状态和DOM

componentDidMount

DOM渲染完成后触发,可以修改DOM,一般用于发送异步请求

componentWillUpdate

state即将更新的时候调用,和componentWillmount一样是低优先级

componentDidUpdate

state更新完成的时候调用

shouldComponentUpdate

可以控制state更改后是否render,是React中可以性能优化的生命周期

shouldComponentUpdate(nextProps, nextState) {
  // 默认值是true,可以通过返回false阻止更新
  if(Josn.strify(this.state) === Josn.strify(nextState)) {
    return false
	}
}

延伸:不能直接修改this.state中的值,因为直接修改了的话,在shouldComponentUpdate中会判断为未修改,会阻止render更新

🍪直接修改this.state,可以改掉state中属性的值,但是不会引发render

componentWillReceiveProps:父组件的重新渲染会调用这个回调函数,无论props变了没有

这个生命周期可以拿到最新的props属性值,是在子组件中使用的,就算父组件传的props没有更新,也会使child组件生命周期更新

componentWillUnMount

组件销毁的时候会调用这个生命周期,可以用来清除事件监听

👶新生命周期

getDerivedStateFromProps:derived是衍生的意思

初始化和state更新以及props更新都能触发该生命周期,可看作是componentWillMountcomponentWillReceiveProps的结合

适用于第一次更新和后续都会更新的逻辑

// 配合componentDidUpdate使用代替
class Test extends React.component{
  contructor() {
    super()
    this.state = {
      name: ''
    }
	}
  
  // 因为是静态方法,所以this是undefined
  static getDerivedStatefromProps(nextProps, nextState) {
    /**
    props和state的改变都会触发这个回调函数的执行,但是react内部会将state变化合并处理(每个事件循环机制之后会合并处理),所以对	       性能的影响不大
    */
    return {
      name: nextState.name
    }
	}
  
  // 可以拿到最新更新完的状态值
  componentDidUpdate(preProps, preState) {
    // 避免重复执行操作
    if (this.state.name === preState.name) {
      return
		}
    console.log(this.state.name)
	}
}

getSnapshotBeforeUpdate:在更新之前获取快照

代替componentWillUpdate,在render之后,componentDidUpdate之前执行,DOM渲染之前执行

class Test extends React.component{
  contructor(){
    super()
		this.state = {
      name: 'huhaha'
    }
	}
  
  componentDidUpdate(preProps, preState, value){
    // value就是getSnapshotBeforeUpdate返回的值
    console.log(value) // 100
	}
  
  getSnapshotBeforeUpdate() {
    return 100
	}
}

十二、React的性能优化🌟

手动优化

通过手动控制shouldComponentUpdate中返回true和false,去决定是否需要触发render

使用纯组件

PureComponent通过对state和props进行浅对比,实现了shouldComponentUpdate

注意⚠️:

  • 如果你的props和state是比较复杂的数据结构,不建议使用PureComponent,可能需要通过forceUpdate去强制组件更新
  • PureComponent中的shouldComponentUpdate会跳过所有子组件的prop的更新,所以子组件也必须是纯组件

函数组件的优化

使用React.memo

十三、React中的hooks

为什么需要用hooks?

  • 高阶组件为了复用,导致层级太深
  • 类组件的生命周期复杂
  • 写成function组件的无状态组件,后续想添加状态,重构起来比较麻烦

useState(状态管理)

⚠️:state的更改会引起整个函数组件的更新,而类组件是只会引起render中的内容更新

import { useState } from 'react'
const App = () => {
	const [num, setNum] = useState(1)
	
	return <div>{num}</div>
}

useEffect(解决函数的副作用)

1⃣️第二个参数中传空数组或者常量,表示只会在DOM挂载完成之后,执行一次,相当于类组件中的componentDidMount

// 页面渲染完成后发送请求
useEffect(() => {
	// 发送异步请求
	...
}, [])

2⃣️第二个参数传依赖的变量,useEffect会监听该变量的变化,变量每次变化useEffect都会执行

// num每次都会加1
const [num, setNum] = useState(1)
useEffect(() => {
	setNum(num + 1)
}, [num])

3⃣️模拟销毁组件前的周期,即componentWillUnmount

useEffect(() => {
	// 函数体
	...
  
  return () => {
    ...
  }
}, [])

问:useEffectuseLayoutEffect有什么区别?

**答:**简单来说,调用时机不同。

useLayoutEffectuseEffect功能类似,区别在于前者是在DOM更新后调用的,后者是在DOM渲染完成后调用的;

一般情况下,官方推荐优先使用useEffectuseLayoutEffect比较适合用于避免页面抖动,即DOM会频繁更新的情况,如果在useEffect中操作DOM过多的话,会引起页面频繁重绘、重排,所以操作DOM建议放在useLayoutEffect

useCallback(记忆函数)

:记忆指的是什么?

:之前说state每次更改都会引起函数组件的更新,按道理state的值每次都会变成初始值,但是结果是每次都能拿到前一次操作的结果值,说明react内部对state进行了缓存,也就是记忆功能

// 对于函数而言,函数组件每次更新,都会被重新定义
const handleClick = useCallback(() => {
  console.log(name)
}, [name])

// 第二个参数的三种情况
- 不传参数:每次都会被重新创建
- 传空数组:不会被重新创建,每次都是拿第一次创建的那个函数
- 传依赖:当依赖发生变化时,才会被重新创建

useMemo(记忆组件)

useMemo完全可以替换useCallback,区别在于useCallback不执行函数,只是返回函数,

而useMemo会执行函数并将函数执行的结果返回,等同于vue中计算属性

useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)

const [num, setNum] = useState(1)
const list = useMemo(() => {
  return num
}, [num])

// 如果第一个参数中的函数没有返回值,useMemo会返回undefind

useRef(保存引用值)

常规作用

和React.createRef作用相同,通过ref获取DOM元素或者获取子组件

保存引用值

**问:**如何在react hooks中保存一个变量?

**答:**由于每次state更新都会引起组件的更新,而有些变量不需要引起视图变化,所以我们希望用普通变量存储,而普通变量没有记忆功能,在组件更新的时候会被重新定义,所以需要使用ref保存普通变量

import { useRef } from 'react'

const Test = props => {
  const nameRef = useRef('测试')
  return <div>{nameRef.current}</div>
}

// 下次组件更新的时候,nameRef.current的值还是‘测试’

useContext(跨级通信)

需要结合类组件中的createContext使用,也是用来实现跨级通信的

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    // 向下传递数据
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 使用数据
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useReducer

当组件状态比较多的时候,逻辑复杂且有很多子组件的情况下,useReducer比useState更适用

替代useState

// 替代useState实现计时器功能
const reducer = (state, action) => {
  switch(action.type) {
    case 'increment':
      return { count: state.count++ }
    case: 'decrement':
      return { count: state.count-- }
    case: 'set':
      return { count: action.payload }
    case: 'reset':
      return init(action.payload)
    default:
      return state
	}
}
  
const initState = { count: 1 }

const init = (state) => { count: state }

const App = () => {
  const [state, dispatch] = useReducer(reducer, initState, init)
  return (
    <div>
      <button onClick={() => dispatch({ type: 'increment' })}>加</button>
      {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>减</button>
      <button onClick={() => dispatch({ type: 'set', payload: { count: 10 } })}>设置为10</button>
      <button onClick={() => dispatch({ type: 'reset', payload: initState })}>重置</button>
    </div>
  )
}

结合useContext使用

当子组件层级比较深的时候,我们可以通过context将reducer的state和dispatch传给子组件,这样子组件之间也可以进行通信

import React, { useContext, useReducer } from 'react'

const reducer = (state, action) => {
  switch(action.type) {
    case 'increment':
      return { count: state.count++ }
    case: 'decrement':
      return { count: state.count-- }
    default:
      return state
	}
}

const initState = { count: 1 }

const GlobalContext = React.createContext()
const App = () => {
  const [state, dispatch] = useReducer(reducer, initState)
  
  return <GlobalContext value={{ state, dispatch }}>
    <Child1/>
    <Child2/>
  </GlobalContext>
}

// 改变state值
const Child1 = () => {
  const { dispatch } = useContext(GlobalContext)
  
  return <button onClick={() => dispatch({ type: 'increment' })}>+</button>
}

// 显示state值
const Child2 = () => {
  const { state } = useContext(GlobalContext)
  
  return <div>{state.count}</div>
}

自定义hooks🌟

当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。

使用场景:输入框搜索内容的时候防抖

// 普通的防抖函数
const debounce = (fun, timeout) => {
  let timeId = null
  return function() {
    clearTimeout(timeId)
    timeId = setTimeout(() => {
      fun.call(this)
    }, timeout)
  }
}

// 自定义hook实现的防抖函数
const useDebounce = (value, delay) => {
  const [debounceValue, setDebounceValue] = useState(value)
  useEffect(() => {
    const timeout = setTimeout(() => setDebounceValue(value), delay)
    return () => clearTimeout(timeout)
	}, [value, delay])
}