【React】关于hooks学习思考笔记

前言

  • 以前一开始学react时候,看的视频比较老,都没有讲hooks的,然后学出来只会写类组件,并且完全不知道咋运作的,都是按套路写。后来越学越深入后自然也就会了hooks。并且对类组件有了另一层理解。
  • 以前我们写类组件,都是class xxx extend React.component对吧,但是为什么写个class就能渲染到页面上?这肯定不正常啊。我们学了语法都知道class里面的render实际是加在这个class的原型对象上,那么class它不可能自己调自己的render方法啊(因为class上没render,prototype有)。但不调render怎么渲染的?这就是因为生成了实例,在有个地方new了我们做的class,实例上就继承了原型上的render方法。
  • 那么重点来了,我们渲染页面需要生成实例吗?

hooks必要性

  • 16.8出的新特性。前言里我就写道,我们渲染页面为什么要生成实例?或者说我们渲染页面的话生成实例是否必要?

  • 在没有框架的时代,我们用js渲染个页面很简单啊,写个函数,然后函数里面生成个dom,往dom上加想要的东西,放到需要的位置,然后把这个函数执行就行了。里面有用实例吗?没有。

  • 那么为什么会产生实例来渲染页面?

  • 实例有什么特点? 实例是类的具现化产物,本质是对象,上面有一些属性而已。

  • 那么如果我们渲染个有状态的页面,比如一个组件是显示状态还是不显示状态,这个状态提供给别的组件使用。这时候按以前写的话很可能要在全局做个属性,一个组件显示不显示去改变这个属性,另一个组件去读这个属性。

  • 当时es6的class才出来,大家想着用起来,由于类的实例对于状态管理很方便,于是就有了类组件。像上一条那种情况,状态写在实例里就不会污染全局,而且便于管理,每个实例都是一个自己的空间。再加上完善的生命周期,看起来好像很不错。

  • 但是后来,大家发现,渲染一个页面其实没必要弄一个实例出来,需要状态的话,闭包也是可以的。如果使用闭包,那么函数组件就能拥有状态,不然状态无法一直存在。我们渲染一个页面,使用实例就会耗性能,因为实例上除了状态还会带出别的东西,比如它的生成类,以及生成类的原型等等。但是通过闭包来解决这些问题就比较巧妙,函数组件可以有需要就渲染,用到什么状态什么生命周期就把什么放闭包里,这样更加灵活。其实闭包和实例本质上说并没太大区别,都是把很多个属性封装在某个地方,只是实例相对于闭包来说功能更多,更完整。当然,更多更完整并不意味着好用,反而可能意味着更难去操纵和管理。

  • hooks主要就是为了解决函数组件的状态而存在的。react从用fiber开始,就把每个组件变成一个fiber,而他们的状态就挂到hooks上,而hooks挂到fiber上。hooks是一个链表结构,每个节点都是用户写在组件的hook。实际usestate之类的状态使用闭包存起来,而说到闭包,就想到redux。没错,useState就是useReducer的语法糖,它最终会返回useReducer。所以react给每个组件的状态管理机,就很像是为每个组件创造了单独的store,并把修改store里属性的方法发出去。这里就不展开说了。

  • 除了解决状态问题,hooks还有其他优势,比如写类组件实际我们写的是类,并不是实例,在调用的时候并不是用户在调用,所以用户根本不清楚this到底是谁,导致混乱。

  • 另外就是它能很好的解耦。在写实例时候,如果有多个状态或者副作用,那么需要配合实例生命周期来写,导致状态组件耦合一起,但如果用hooks,可以很好的解耦,相同的依赖可以写在一起。

  • 差不多就写这些,下面学习用法:

useState

  • useState就跟以前写类组件搞个state然后setState差不多,不过这个设计想法倒是拐得到挺远,它是类似redux那种思路,把state和dispatch返回给你。学过redux的看到这立马就会使用了。
  • 使用函数组件和类组件写个数字加一类比下:
import React from 'react';
import { useState } from 'react';
function App(props) {
  return (
    <div>
      <Counter1></Counter1>
      <Counter2></Counter2>
    </div>
  )
}
export default App;
class Counter1 extends React.Component {
  state = { number: 0 }
  render() {
    return (
      <div>
        {this.state.number}
        <button onClick={() => this.setState({ number: this.state.number + 1 })}> ++</button>
      </div>
    )
  }
}
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  return (
    <div>
      <div>
        {state.number}
        <button onClick={() => setState({ number: state.number + 1 })}> ++</button>
      </div>
    </div>
  )

有些细节需要注意:
1、setState里面的对象会覆盖以前的state。比如初始值除了number还设了个name,但是setState的对象里不传name,就会没了。
2、异步逻辑需要注意状态。比如我在组件里使用setTimeout延迟+1,期间我又同步加一几次,但是异步时间到后触发时拿到的state是我还没同步加一的state。最终state又会改为异步执行后的结果。而不是加上同步执行后的结果。这个问题有2种解决方法。

  • 第一种方法 setState传入函数:
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  let add = () => {
    setState({ number: state.number + 1 })
  }
  let asyncAdd = () => {
    setTimeout(() => {
      setState((lastState) => ({ number: lastState.number + 1 }))
    }, 4000);
  }
  return (
    <div>
      <div>
        {state.number}
        <button onClick={add}> ++</button>
        <button onClick={asyncAdd}>async</button>
      </div>
    </div>
  )
}
  • 传入的函数能接收到最新的state,使得其能拿到最新的值。

  • 第二种方法 useRef解决:

function Counter2(props) {
  let refNumber = useRef()
  let [state, setState] = useState({ number: 0 })
  let add = () => {
    setState({ number: state.number + 1 })
    refNumber.current = state.number + 1
  }
  let asyncAdd = () => {
    setTimeout(() => {
      setState({ number: refNumber.current + 1 })
    }, 4000);
  }
  return (
    <div>
      <div>
        {state.number}
        <button onClick={add}> ++</button>
        <button onClick={asyncAdd}>async</button>
      </div>
    </div>
  )
}
  • 这个useRef和createRef很像,区别在于React.createRef返回都是新对象,而useRef每次都是同一个对象。可以试试如果这里使用React.createRef是不行的。
  • 这玩意有点像组件的全局变量,每次都可以拿到它。

3、useState可以传入函数进行惰性初始化。

  • 这个惰性初始化前面在redux那篇里写过,还专门把源码翻出来。再复习下,就是connect里面第二个参数是mapDispatchToProps,内部实现会使用bindActionCreators。这玩意是为了使组件在属性上拿到dispatch action的方法,由于每次渲染都会走一遍组件,直接执行这个函数或者普通写法导致重新绑定action浪费性能。所以使用惰性初始化。
  • 其实学了上面useRef感觉好像可以用useRef代替useState的惰性初始化,但实际情况是不行的,useRef的初始值传入函数会每次执行。虽然给你的是一个对象。
  • 所以这个惰性初始化主要玩的还是里面那个函数,如果需要那个函数每次渲染就执行一次,就可以放进去。

4、setState传和之前相同的对象,则组件不会更新。注意,这里说的同一个是指直接拿这个state传给它,而不是声明一个新变量或者解构后的值和原来相同。
5、react需要保证每次渲染钩子数量一样,所以钩子只能放函数里的外层。不能放到条件或者内嵌函数里面。

useCallback

  • 每次渲染会导致组件里函数跟上次不是一个函数,可以使用useCallback进行缓存:
let previousFunc
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  let add = useCallback(() => {
    setState({ number: state.number + 1 })
    console.log('funcgo');
  }, [])
  console.log(previousFunc === add);
  previousFunc = add
  return (
    <div>
      <div>
        {state.number}
        <button onClick={add}> ++</button>
      </div>
    </div>
  )
}
  • 第一次打印false,后面都会打印true。如果把useCallback去了,打印会发现每次都是false。
  • 但是会发现,数字只执行了一次就不能加了。因为函数缓存的时候那个state变成了闭包,所以跟useEffect一样,可以放依赖变量来进行更新函数。
 let add = useCallback(() => {
    setState({ number: state.number + 1 })
    console.log('funcgo');
  }, [state.number])
  • 所以这里就有个很好的优化点。一个函数组件里面可能会有多个useState,然后就有多个state,别的state更新跟你这个方法没关系,所以就可以不用更新方法,进行缓存。轮到你的state变化了,再进行更新方法。

useMemo

  • 我第一次听这玩意还以为是啥牛b的东西。原来memo就是memory缩写。
  • 这个是为了解决按需渲染的问题。
  • 一般情况,父组件刷新了子组件会跟着刷新,但是子组件明明状态没改变干嘛要刷新,所以就可以使用这个方法。
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  let [state2, setState2] = useState({ number: 0 })
  let add = useCallback(() => {
    setState({ number: state.number + 1 })
  }, [state.number])
  let add2 = useCallback(() => {
    setState2({ number: state2.number + 1 })
  }, [state2.number])
  let data = { number: state.number + 1 }//模仿需要父组件数据进行处理
  return (
    <div>
      <div>
        {state.number}
        <button onClick={add}> ++</button>
      </div>
      <div>
        {state2.number}
        <button onClick={add2}> ++22++</button>
      </div>

      <Child onButtonClick={add} data={data}></Child>
    </div>
  )
}
function Child(props) {
  const { onButtonClick, data } = props
  console.log('child');
  return (
    <div>
      <div>
        {data.number}
        <button onClick={onButtonClick}> ++</button>
      </div>
    </div>
  )
}
  • 我们先复现下场景。
  • counter2里面有2个状态,都在counter2上显示出来。然后counter2还渲染另一个子组件,并把状态处理下传给子组件。
  • 可以发现,在父组件无论使用哪个state的方法加一,都会使得子组件刷新。事实上子组件只依赖state,跟state1没关系。
  • 于是就可以使用memo优化:
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  let [state2, setState2] = useState({ number: 0 })
  let add = useCallback(() => {
    setState({ number: state.number + 1 })
  }, [state.number])
  let add2 = useCallback(() => {
    setState2({ number: state2.number + 1 })
  }, [state2.number])
  //let data = { number: state.number + 1 }//模仿需要父组件数据进行处理
  let data = useMemo(() => ({ number: state.number + 1 }), [state])
  return (
    <div>
      <div>
        {state.number}
        <button onClick={add}> ++</button>
      </div>
      <div>
        {state2.number}
        <button onClick={add2}> ++22++</button>
      </div>

      <Memochild onButtonClick={add} data={data}></Memochild>
    </div>
  )
}
function Child(props) {
  const { onButtonClick, data } = props
  console.log('child');
  return (
    <div>
      <div>
        {data.number}
        <button onClick={onButtonClick}> ++</button>
      </div>
    </div>
  )
}
let Memochild = memo(Child)
  • 就是把组件用memo包一层。这个memo就是个高阶组件,然后渲染它。
  • 如果没有注释的那句话,直接把state传给data,那么点击add2,子组件不会刷新。由于模拟了对父组件状态进行处理,所以每次父组件刷新都会去执行赋值,导致子组件刷新。
  • 于是使用useMemo对其进行缓存。
  • 其实useCallback能达成和useMemo一样的效果:
let data = useCallback(({ number: state.number + 1 }), [state])
  • 改了后一样子组件不会刷新。

useReducer

  • 这玩意就跟redux那套差不多。没懂的可以先去看redux。
const [state, dispatch] = useReducer(reducer, initialArg, init);
  • 接收三个参数,reducer就是自己弄个reducer。
  • initialArg是初始参数
  • init是初始函数。初始参数会传给初始函数。
function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { number: state.number + 1 }
    case 'MINUS':
      return { number: state.number - 1 }
  }
}
function initialFunc(initialArg) {
  return { number: initialArg }
}
function Counter2(props) {
  let [state, dispatch] = useReducer(reducer, 0, initialFunc)
  return (
    <div>
      <div>
        {state.number}
        <button onClick={() => dispatch({ type: 'ADD' })}> ++</button>
        <button onClick={() => dispatch({ type: 'MINUS' })}> --</button>
      </div>
    </div>
  )
}
  • 可以发现useReducer可以自己操作的地方变多了,非常实用,比较适合新状态复杂的情况,如果简单就用useState。
  • 最后那个初始函数可以不传,直接在第二个参数返回对象就可以了。

useContext

  • 这个用的挺多的,store那些都是通过这个传的。
  • 用起来也很简单,可以这样记忆:我需要用React.createContext来创建一个闭包空间。这玩意还自带一个Provider用来包裹子组件,所有子组件可以通过useContext传入前面的闭包空间来获取里面的东西。
let Context = React.createContext()
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  return (
    <Context.Provider value={{ state, setState }}>
      <Child></Child>
    </Context.Provider>
  )
}
function Child(props) {
  let { state, setState } = useContext(Context)
  return (
    <div>
      {state.number}
      <button onClick={() => setState({ number: state.number + 1 })}> ++</button>
    </div>
  )
}

useEffect

  • 这个钩子可以实现以前didmount、didupdate,willunmount的功能。
  • 我们可以先用类组件写个把state渲染到标题的副作用,对比函数组件看一下:
class Counter1 extends React.Component {
  state = { number: 0 }
  componentDidMount() {
    document.title = this.state.number + ''
  }
  componentDidUpdate() {
    document.title = this.state.number + ''
  }
  render() {
    return (
      <div>
        {this.state.number}
        <button onClick={() => this.setState({ number: this.state.number + 1 })}> ++</button>
      </div>
    )
  }
}
  • 可以看见,didmount需要变一下,didupdate就是更新状态后还得写一遍。
  • 函数组件就不一样了:
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  useEffect(() => {
    document.title = state.number + ''
  })
  return (
    <div>
      <p>
        {state.number}
      </p>
      <button onClick={() => setState({ number: state.number + 1 })}>++++</button>
    </div>
  )
}
  • 这个就一个useEffect抵得上原来2个地方都要写。
  • 如果只想要didmount的效果,那就给useEffect第二个参数再传个空数组。这样就只会didmount渲染次就没了。
  • 如果只想要didupdate的效果,那么找个变量做个判断,初次渲染不加就可以了:
  useEffect(() => {
    if (!mount) {
      mount = true
    } else {
      document.title = state.number + ''
    }
  })
  • 这样单独要其中一种效果或者2种效果都要就都可以实现。
  • 第二个参数是依赖项,可以看成条件执行函数的简写。相当于每次要渲染时对比数组里元素和上次渲染的元素是否相同,空数组就代表始终相同。相同就不会执行,不相同就会执行。
  • useEffect的清除副作用时靠返回值:
function Counter2(props) {
  let [state, setState] = useState({ number: 0 })
  useEffect(() => {
    let timer = setInterval(() => {
      setState(prev => ({ number: prev.number + 1 }))
    }, 1000);
    return () => { clearInterval(timer) }
  })
  return (
    <div>
      <p>
        {state.number}
      </p>
      <button onClick={() => setState({ number: state.number + 1 })}>++++</button>
    </div>
  )
}
  • 这里开个定时器,然后每秒加一。如果不传空数组,这个useEffect每次都会因刷新而执行。如果没有返回值,setInterval每次执行产生的计时器在内存里就会越来越多。所以要提供清除它计时器的方式。这个清除方式一般叫unEffect函数。
  • 每次执行effect会先执行下unEffect,这样就能确保只有一个定时器在跑了。

useRef

  • 这个useRef前面在useState里面讲了点。
  • 这个还可以获得dom引用拿到dom
function Counter2(props) {
  let inputRef = useRef()
  let getFocus = () => {
    inputRef.current.focus()
  }
  return (
    <div>
      <p>
        <input type="text" ref={inputRef}></input>
      </p>
      <button onClick={getFocus}>++焦点++</button>
    </div>
  )
}
  • 另外,useRef可以作为别的属性传递给子组件。ref为保护属性,函数组件是不能使用ref属性的。类组件可以使用ref属性,因为其一直存在。
function Counter2(props) {
  let inputRef = useRef()
  let getFocus = () => {
    inputRef.current.focus()
  }
  return (
    <div><Child ref22={inputRef}></Child>
      <button onClick={getFocus}>++焦点++</button>
    </div>
  )
}
function Child(props) {
  return (
    <div>
      <p>
        <input type="text" ref={props.ref22}></input>
      </p>
    </div>
  )
}
  • 还有种方法可以达到相同的效果,就是使用fowardRef将子组件包装下:
function Counter2(props) {
  let inputRef = useRef()
  let getFocus = () => {
    inputRef.current.focus()
  }
  return (
    <div><Forwarded ref={inputRef}></Forwarded>
      <button onClick={getFocus}>++焦点++</button>
    </div>
  )
}
function Child(props, ref) {
  return (
    <div>
      <p>
        <input type="text" ref={ref}></input>
      </p>
    </div>
  )
}
let Forwarded = forwardRef(Child)
  • 这样传入函数组件时除了Props还会收到ref,父组件就能拿到子组件的dom了。

useImperativeHandle

  • 由于前面使用useRef会使得父组件拿到子组件的dom,除了focus还可以使用别的方法,比如value就是给input输入框添加字符串。
  • 但是这样父组件权限太高,于是就会有这个钩子将父组件传给子组件的ref值替换调,让父组件调用已替换里面的方法,这样,父组件就不能调用子组件用钩子定义之外的方法了。
  • 其实就是给父组件增加操作子组件的权限。
function Counter2(props) {
  let inputRef = useRef()
  let getFocus = () => {
    inputRef.current.focus()//方法名对应handle的对象里方法
    inputRef.current.value = 'yyyy'//由于ref拿到的不是dom所以没有value就不会成功,优点可以不报错。
  }
  return (
    <div><Forwarded ref={inputRef}></Forwarded>
      <button onClick={getFocus}>++焦点++</button>
    </div>
  )
}
function Child(props, ref) {
  let newRef = useRef()
  useImperativeHandle(ref, () => ({
    focus() {//父组件调用方法名,自己取
      newRef.current.focus()//实际操作的方法
    }
  }))
  return (
    <div>
      <p>
        <input type="text" ref={newRef}></input>
      </p>
    </div>
  )
}
let Forwarded = forwardRef(Child)
  • 这个就很像以前我们经常用的Object.defineProperty。在访问这个对象时修改对象的get和set,增加额外逻辑。
  • 其实就是父组件ref的current = 回调函数的返回值。

useLayoutEffect

  • 讲这个钩子必须先将下浏览器运行顺序。
  • 一般来说,浏览器是有个html解析器产生dom树,还有个css解析器产生css规则,两者结合生成render tree。注意,此时还没开始渲染,只拿到render tree。useLayoutEffect就是在这时候工作的。然后下一步才开始是绘制出页面。然后绘制好了就是useEffect工作。
  • 我们使用alert可以阻塞的特性来看一下:
function Counter2(props) {
  let [color, setState] = useState('red')
  useEffect(() => {
    alert('useEffect' + color)
  })
  useLayoutEffect(() => {
    alert('useLayoutEffect' + color);
  })
  return (
    <div>
      <div style={{ backgroundColor: color }}>xxxxxxxxxxxxxxxx  </div>
      <button onClick={() => setState('yellow')}></button>
      <button onClick={() => setState('green')}>绿</button>
    </div>
  )
}
  • 这里alert的颜色2者都是一样的。但是区别就是,useLayoutEffect的alert时颜色还没被换掉。而useEffect是在颜色已经换掉后才alert。
  • 特别注意,useLayoutEffect在alert时虽然颜色没替换掉,但是拿到的render tree已经是最新的了,所以直接去取dom也是最新的状态,而不是你看见的老状态。

自定义hook

  • 如果函数的名字以 use 开头,并且调用了其他的 Hook,则就称其为一个自定义 Hook。
  • 注意:自定义hook它是实现逻辑复用,而不是状态复用。
  • 就类似你执行后生成各自独有的闭包一样。
function useCounter() {
  let [number, setNumber] = useState(0)
  useEffect(() => {
    setInterval(() => {
      setNumber(prev => prev + Math.random())
    }, 1000);
  }, [])
  return number
}
function Counter1(props) {
  let number = useCounter()
  return (
    <div>
      {number}
    </div>
  )
}
function Counter2(props) {
  let number = useCounter()
  return (
    <div>
      {number}
    </div>
  )
}
  • 注意,这个自定义hook不用use开头会报错

  • 这个自定义hooks可以用来包装useReducer做成logger中间件:

function useLogger(reducer, initialState, init) {
    const [state, dispatch] = useReducer(reducer, initialState, init);
    let dispatchWithLogger = (action) => {
        console.log('old state', state);
        dispatch(action);
    }
    useEffect(function () {
        console.log('new state', state);
    }, [state]);
    return [state, dispatchWithLogger];
}
  • 老状态简单,就是dispatch前加自己逻辑。新状态就得用useEffect。在状态变更后加自己逻辑。
  • 还可以做成promise的中间件
function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { number: state.number + 1 }
    case 'MINUS':
      return { number: state.number - 1 }
  }
}
function usePromise() {
  let [state, dispatch] = useReducer(reducer, { number: 0 })
  let mydispatch = (action) => {
    action.then(res => {
      dispatch(res)
    })
  }
  return [state, mydispatch]
}
function Counter2(props) {
  let [state, dispatch] = usePromise()
  let promiseadd = () => {
    dispatch(new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ type: 'ADD' })
      }, 1000);
    }))
  }
  return (
    <div>
      {state.number}
      <button onClick={promiseadd}>promise+</button>
    </div>
  )
  • 主要就是在点击时派发一个promise,模拟去取数据,获取完数据后再派发修改状态的操作。
  • usePromise就是自己定义个方法把dispatch包装下,跟上面思路大同小异,运行then后拿到resolve的action进行派发。
  • 还有派发函数的中间件thunk:
function useThunk() {
  let [state, dispatch] = useReducer(reducer, { number: 0 })
  let mydispatch = (action) => {
    if (typeof action === 'function') {
      action(dispatch, () => state)
    } else {
      dispatch(action)
    }
  }
  return [state, mydispatch]
}
function Counter2(props) {
  let [state, dispatch] = useThunk()
  let thunkadd = () => {
    dispatch(function (thunkdispatch, getstate) {
      setTimeout(() => {
        thunkdispatch({ type: 'ADD' })
      }, 1000);
    })
  }
  return (
    <div>
      {state.number}
      <button onClick={thunkadd}>thunkadd+</button>
    </div>
  )
}
  • 都是一个套路。就是dispatch过去可能function,就执行它,否则就派发。上面的promise则是看action有没有then方法,有then暂且认为它是promise,那么就调then把其resolve的action派发。
  • 还有请求自定义hook的写法:
function Counter1(props) {
  const [users, loadMore] = useRequest('http://localhost:8000/api/users');
  if (users === null) {
    return <div>加载中....</div>
  }
  return (
    <>
      <ul>
        {
          users.map((item, index) => <li key={index}>{item.id}:{item.name}</li>)
        }
      </ul>
      <button onClick={loadMore}>加载更多</button>
    </>
  )
}
function useRequest(url) {
  let limit = 5
  let [offset, setOffset] = useState(0)//偏移,从第几个开始加载
  let [loading, setloading] = useState(false)//用来控制useEffect调用,保证点击按钮后执行。
  let [data, setData] = useState([]);//数据存放
  useEffect(() => {
    async function reqfunc() {
      setData(null)//用来显示加载中
      let newData = await fetch(`${url}?offset=${offset}&limit=${limit}`).then(res => res.json())
      setData([...data, ...newData])//合并
      setOffset(offset + newData.length)
    }
    reqfunc()
  }, [loading])
  function loadMore() {
    setloading(!loading)
  }
  return [data, loadMore];
}
  • 这里有争议的就是setData(null)之后为啥还能解构它。其实就是setData后是不能马上拿到新状态,拿的还是老状态,所以前面自定义hook第一个logger例子里马上取就取不到,通过useEffect才拿到修改后的状态。

  • 还可以动态修改类名做成动画:

  • 先写个css:

.circle{
  width:50px;
  height: 50px;
  border-radius: 50%;
  background-color: red;
  transition: all 1s; 
}
.circle-bigger{
  width: 200px;
  height: 200px;
}
.circle-smaller{
  width: 10px;
  height: 10px;
}
  • 这样添加类名就能变大变小了,都要包含circle这个className。
function useAnimation(initialClassName) {
  const [className, setClassName] = useState(initialClassName);//修改类名
  function bigger() {
    setClassName(`${initialClassName} ${initialClassName}-bigger`);
  }
  function smaller() {
    setClassName(`${initialClassName} ${initialClassName}-smaller`)
  }
  return [className, bigger, smaller];
}
function Counter1(props) {
  const [className, bigger, smaller] = useAnimation('circle');
  return (
    <div>
      <button onClick={bigger}>bigger</button>
      <button onClick={smaller}>smaller</button>
      <div className={className}></div>
    </div>
  )
}
  • 点击按钮试试,可以变大变小即为成功。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/104149172