1、useState:让函数式组件拥有状态
import { useState } from 'react'
const Test = ( ) => {
const [ count, setCount] = useState ( 0 ) ;
return (
< >
< h1> 点击了{ count} 次< / h1>
< button onClick= { ( ) => setCount ( count + 1 ) } > + 1 < / button>
< / >
) ;
}
export default Test
PS:class组件中this.setState更新是state是合并, useState中setState是替换。例如:
import { useState } from 'react'
const Test = ( ) => {
const [ counts, setCounts] = useState ( {
num1 : 0 ,
num2 : 0
} ) ;
return (
< >
< h1> num1:{ counts. num1} < / h1>
< h1> num2:{ counts. num2} < / h1>
< button onClick= { ( ) => setCounts ( { num1 : counts. num1 + 1 } ) } > num1+ 1 < / button>
< button onClick= { ( ) => setCounts ( { num2 : counts. num2 + 1 } ) } > num2+ 1 < / button>
< / >
) ;
}
export default Test
可以看到useState中setState是替换,不会合并,正确更新:
import { useState } from 'react'
const Test = ( ) => {
const [ counts, setCounts] = useState ( {
num1 : 0 ,
num2 : 0
} ) ;
return (
< >
< h1> num1:{ counts. num1} < / h1>
< h1> num2:{ counts. num2} < / h1>
< button onClick= { ( ) => setCounts ( { ... counts, num1 : counts. num1 + 1 } ) } > num1+ 1 < / button>
< button onClick= { ( ) => setCounts ( { ... counts, num2 : counts. num2 + 1 } ) } > num2+ 1 < / button>
< / >
) ;
}
export default Test
2、useEffect:副作用,取代生命周期
可以绑定触发更新的依赖状态,默认是状态中任何数据发生变化副作用都会执行,如:
import { useState, useEffect } from 'react'
const Test = ( ) => {
const [ count1, setCount1] = useState ( 0 ) ;
const [ count2, setCount2] = useState ( 0 ) ;
useEffect ( ( ) => {
console. log ( 'useEffect触发了' )
} ) ;
return (
< >
< h1> count1:{ count1} < / h1>
< h1> count2:{ count2} < / h1>
< button onClick= { ( ) => setCount1 ( count1 + 1 ) } > count1+ 1 < / button>
< button onClick= { ( ) => setCount2 ( count2 + 1 ) } > count2+ 1 < / button>
< / >
) ;
}
export default Test
将上述代码useEffect第二个参数传入需要绑定的状态,可绑定多个:
useEffect ( ( ) => {
console. log ( 'useEffect触发了' )
} , [ count1] ) ;
可以看到,只有绑定的count1发生变化才会触发,如果传空数组则任何状态发生变化都不会触发,此时useEffect的作用就类似class组件中的componentDidMount,所以发送请求通常也会在此执行。
清理副作用 在上面的操作中都不用清理的副作用,然而,有些副作用是需要去清理的,不清理会造成异常甚至内存泄漏,比如开启定时器,如果不清理,则会多次开启,从上面可以看到useEffect的第一个参数是一个回调函数,可以在回调函数中再返回一个函数,该函数可以在状态更新后第一个回调函数执行之前调用,具体实现:
useEffect ( ( ) => {
return ( ) => {
}
} ) ;
3、useContext:跨组件共享数据
React.createContext();创建一个TestContext对象 TestContext.Provider包裹子组件 数据放在<TestContext.Provider value={value}>的value中 子组件中通过useContext(TestContext)获取值
import React, { useContext, useState } from 'react' ;
const TestContext = React. createContext ( ) ;
const Parent = ( ) => {
const [ value, setValue] = useState ( 0 ) ;
return (
< div>
{ ( ( ) => console. log ( "Parent-render" ) ) ( ) }
< button onClick= { ( ) => setValue ( value + 1 ) } > value + 1 < / button>
< TestContext. Provider value= { value} >
< Child1 / >
< Child2 / >
< / TestContext. Provider>
< / div>
) ;
}
const Child1 = ( ) => {
const value = useContext ( TestContext) ;
return (
< div>
{ ( ( ) => console. log ( 'Child1-render' ) ) ( ) }
< h3> Child1- value: { value} < / h3>
< / div>
) ;
}
const Child2 = ( ) => {
return (
< div>
{ ( ( ) => console. log ( 'Child2-render' ) ) ( ) }
< h3> Child2< / h3>
< / div>
) ;
}
export default Parent
至此数据实现共享了,但是可以看到在TestContext中的共享数据只要发生变化,子组件都会重新渲染,Child2并没有绑定数据,不希望他做无意义的渲染,可以使用React.memo解决,实现:
const Child2 = React. memo ( ( ) => {
return (
< div>
{ ( ( ) => console. log ( 'Child2-render' ) ) ( ) }
< h3> Child2< / h3>
< / div>
) ;
} ) ;
4、useCallback:性能优化
const handleClick = useCallback ( ( ) => {
} , [ value] ) ;
useCallback返回的是一个 memoized(缓存)函数,在依赖不变的情况下,多次定义的时候,返回的值是相同的,他的实现原理是当使用一组参数初次调用函数时,会缓存参数和计算结果,当再次使用相同的参数调用该函数时,会直接返回相应的缓存结果。 优化性能例子:
import React, { useState, useCallback, memo } from 'react' ;
const Parent = ( ) => {
const [ value1, setValue1] = useState ( 0 ) ;
const [ value2, setValue2] = useState ( 0 ) ;
const handleClick1 = useCallback ( ( ) => {
setValue1 ( value1 + 1 ) ;
} , [ value1] ) ;
const handleClick2 = useCallback ( ( ) => {
setValue2 ( value2 + 1 ) ;
} , [ value2] ) ;
return (
< >
{ ( ( ) => console. log ( "Parent-render" ) ) ( ) }
< h3> { value1} < / h3>
< h3> { value2} < / h3>
< Child1 handleClick1= { handleClick1} / >
< Child2 handleClick2= { handleClick2} / >
< / >
) ;
}
const Child1 = memo ( props => {
return (
< div>
{ ( ( ) => console. log ( "Child1-render" ) ) ( ) }
< button onClick= { ( ) => props. handleClick1 ( ) } > value1 + 1 < / button>
< / div>
) ;
} ) ;
const Child2 = memo ( props => {
return (
< div>
{ ( ( ) => console. log ( "Child2-render" ) ) ( ) }
< button onClick= { ( ) => props. handleClick2 ( ) } > value2 + 1 < / button>
< / div>
) ;
} ) ;
export default Parent
useCallback返回的是一个memoized回调函数,仅在其中绑定的一个依赖项变化后才更改可防止不必要的渲染,在跨组件共享数据中举例的事件是在父组件中点击触发,而现在是使用状态提升,在父组件中传递方法供子组件调用,每次render时函数也会变化,导致子组件重新渲染,上面例子useCallback将函数进行包裹,依赖值未发生变化时会返回缓存的函数,配合React.memo即可优化无意义的渲染。
5、useMemo:性能优化
useMemo ( ( ) => {
} , [ value] ) ;
import React, { useState } from 'react'
const Test = ( ) => {
const [ value, setValue] = useState ( 0 ) ;
const [ count, setCount] = useState ( 1 ) ;
const getDoubleCount = ( ) => {
console. log ( 'getDoubleCount进行计算了' ) ;
return count * 2 ;
} ;
return (
< div>
< h2> value: { value} < / h2>
< h2> doubleCount: { getDoubleCount ( ) } < / h2>
< button onClick= { ( ) => setValue ( value + 1 ) } > value+ 1 < / button>
< / div>
)
}
export default Test
可以看到getDoubleCount依赖的是count,但value发生变化它也重新进行了计算渲染,现在只需要将getDoubleCount使用useMemo进行包裹,如下:
import React, { useState, useMemo } from 'react'
const Test = ( ) => {
const [ value, setValue] = useState ( 0 ) ;
const [ count, setCount] = useState ( 1 ) ;
const getDoubleCount = useMemo ( ( ) => {
console. log ( 'getDoubleCount进行计算了' ) ;
return count * 2 ;
} , [ count] ) ;
return (
< div>
< h2> value: { value} < / h2>
< h2> doubleCount: { getDoubleCount} < / h2>
< button onClick= { ( ) => setValue ( value + 1 ) } > value+ 1 < / button>
< / div>
)
}
export default Test
现在getDoubleCount只有依赖的count发生变化时才会重新计算渲染。
useMemo和useCallback的共同点:
接收的参数都是一样的,第一个是回调函数,第二个是依赖的数据 它们都是当依赖的数据发生变化时才会重新计算结果,起到了缓存作用
useMemo和useCallback的区别:
useMemo计算结果是return回来的值,通常用于缓存计算结果的值 useCallback计算结果是一个函数,通常用于缓存函数
6、useRef
例如要实现点击button按钮使input输入框获得焦点:
import React, { useRef } from 'react' ;
const Test = ( ) => {
const inputEl = useRef ( ) ;
return (
< >
< input ref= { inputEl} / >
< button onClick= { ( ) => inputEl. current. focus ( ) } > focus< / button>
< / >
) ;
}
export default Test
这样看起来非常像React.createRef(),将上面代码中的useRef()改成React.createRef()也能实现同样的效果,那为什么要设计一个新的hook?难道只是为了加上use,统一hook规范? 事实上,它们确实不一样。 官网的说明如下: useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
简单来说,useRef就像一个储物箱,你可以随意存放任何东西,再次渲染时它会去储物箱找,createRef每次渲染都会返回一个新的引用,而useRef每次都会返回相同的引用。