useState
基本结构
const [state, setState] = useState(initState);
1,state为你要设置的状态
2,setState为更新state的方法,命名随意
3,initState为初始的state,可以是任意数据类型,也可以是回调函数,但必须有返回值
import { useState } from "react";
const Greeting = (props) => {
const [name, setName] = useState('li');
const [subname, setSubName] = useState('lisha');
function handleNameChange(e){
setName(e.target.value)
}
function handleSubNameChange(e) {
setSubName(e.target.value)
}
return (
<React.Fragment>
<p>{name}</p>
<input onChange={handleNameChange}/>
<p>{subname}</p>
<input onChange={handleSubNameChange} />
</React.Fragment>
)
}
export default Greeeting;
调用useState,传入初始值,通过数组的结构赋值得到独立的local state name,及setName。name可以理解为class.component中的state,可见这里的state不局限于对象,可以为number,string,当然也可以是一个对象。而setCount可以理解为class.component中的setState,不同的是setState会merge新老state,而hook中的set函数会直接替换,这就意味着如果state是对象时,每次set应该传入所有属性,而不能像class.component那样仅传入变化的值。所以在使用useState时,尽量将相关联的,会共同变化的值放入一个object。
我们知道当函数执行完毕,函数作用域内的变量都会销毁,hooks中的state在component首次render后被React保留下来了。那么在下一次render时,React如何将这些保留的state与component中的local state对应起来呢。
const stateArr = []
const setterArr = []
let cursor = 0
let isFirstRender = true
function createStateSetter(cursor) {
return state => {
stateArr[cursor] = state
}
}
function useState(initState) {
if (isFirstRender) {
stateArr.push(initState)
setterArr.push(createStateSetter(cursor))
isFirstRender = false
}
const state = stateArr[cursor]
const setter = setterArr[cursor]
cursor++
return [state, setter]
}
可以看出React需要保证多个hooks在component每次render的时候的执行顺序都保持一致,否则就会出现错误。这也是React hooks rule中必须在top level使用hooks的由来————条件,遍历等语句都有可能会改变hooks执行的顺序。
useEffect
基本结构
useEffect(callback,array)
1,callback:回调函数,用于处理副作用逻辑。
2,array(可选):一个数组,用于控制执行。
useEffect 会在每一次render之后执行,包括初始render和更新render。不阻塞渲染。
react中有两种常见的副作用,一种是需要清理的,一种是不需要清理的:
1、网络请求、DOM修改和日志记录等,这些是不需要清理的。useEffect会自动处理。
2、订阅和取消订阅,事件监听和取消事件监听,这种是需要清理的。
下面是useEffect清理机制:
1、useEffect在每次执行之前都会自动清理之前的effect。
2、effect中可以返回一个函数用于清理工作
//class.Component写法
class Greeting extends React.PureComponent{
static contextType = ThemeContext;
state={
name: 'li',
subname: 'sha',
width: window.innerWidth,
}
componentDidMount(){
document.title = this.state.name + ' ' + this.state.subname;
window.addEventListener('resize', this.handleWidthChange);
}
componentDidUpdate(){
document.title = this.state.name + ' ' + this.state.subname;
}
componentWillMount(){
window.removeEventListener('resize', this.handleWidthChange);
}
handleWidthChange = () => {
this.setState({width: window.innerWidth});
}
handleNameChange = (e)=>{
this.setState({name: e.target.value});
}
handleSubNameChange = (e) => {
this.setState({ subname: e.target.value });
}
render(){
const theme = this.context;
return(
<div style={{ ...theme}}>
<p>{this.state.name}</p>
<input onChange={this.handleNameChange}/>
<p>{this.state.subname}</p>
<input onChange={this.handleSubNameChange} />
<p>width:</p>
<p>{this.state.width}</p>
</div>
);
}
}
useEffect Hook的用法
在传入useEffect的函数(effect)中做了一些"side effect",在class.component中我们通常会在componentDidMount,componentDidUpdate中去做这些事情。另外在class.component中,需要在componentDidMount中订阅,在componentWillUnmount中取消订阅,这样将一件事拆成两件事做,不仅可读性低,还容易产生bug。hook中就会把相关联的事情放在一起处理。
const Greeting = (props) => {
const name = useFormInput('li');
const subname = useFormInput('lisha');
const theme = useContext(ThemeContext);
const width = useWindowWidth();
useDocumentTitle(name.value + ' ' + subname.value);
return (
<div style={{...theme}}>
<p>{name.value}</p>
<input {...name}/>
<p>{subname.value}</p>
<input {...subname} />
<p>width:</p>
<p>{width}</p>
</div>
)
}
function useFormInput(initValue){
const [value, setValue] = useState(initValue);
function handleChange(e) {
setValue(e.target.value)
}
return {
value,
onChange: handleChange,
};
}
function useDocumentTitle(title){
useEffect(() => {
document.title = title;
}, [title]);
}
function useWindowWidth(){
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleWidthChange = () => {
setWidth(window.innerWidth);
}
window.addEventListener('resize', handleWidthChange);
return () => {
window.removeEventListener('resize', handleWidthChange);
};
}, [width]);
return width;
}
每次render后都会执行effect,这样会不会对性能造成影响。其实effect是在页面渲染完成之后执行的,不会阻塞,而在effect中执行的操作往往不要求同步完成,除了少数如要获取宽度或高度,这种情况需要使用其他的hook(useLayoutEffect),此处不做详解。即使这样,React也提供了控制的方法,及useEffect的第二个参数————一个数组,如果数组中的值不发生变化的话就跳过effect的执行。
useContext
//Context.tsx
import React from 'react';
export const themes = {
light: {
color: '#ffffff',
background: '#222222',
},
dark: {
color: '#000000',
background: '#eeeeee',
},
};
export const ThemeContext = React.createContext(themes.light);
//Page.tsx
import { useState, useEffect, useContext} from "react";
import { ThemeContext, themes} from './Context';
const Page = (props) => {
let [theme, setTheme] = useState('light');
return (
<>
<ThemeContext.Provider value={themes[theme]}>
<button onClick={(e) => { setTheme('dark') }}>dark</button>
<button onClick={(e) => { setTheme('light') }}>light</button>
<Greeting />
</ThemeContext.Provider>
</>
);
};
export default Page;
class.Component写法
class Greeting extends React.PureComponent{
static contextType = ThemeContext;
state={
name: 'li',
subname: 'sha',
}
handleNameChange = (e)=>{
this.setState({name: e.target.value});
}
handleSubNameChange = (e) => {
this.setState({ subname: e.target.value });
}
render(){
const theme = this.context;
return(
<div style={{ ...theme}}>
<p>{this.state.name}</p>
<input onChange={this.handleNameChange}/>
<p>{this.state.subname}</p>
<input onChange={this.handleSubNameChange} />
</div>
);
}
}
useContext Hook写法
const Greeting = (props) => {
const [name, setName] = useState('li');
const [subname, setSubName] = useState('lisha');
const theme = useContext(ThemeContext);
function handleNameChange(e){
setName(e.target.value)
}
function handleSubNameChange(e) {
setSubName(e.target.value)
}
return (
<div style={{...theme}}>
<p>{name}</p>
<input onChange={handleNameChange}/>
<p>{subname}</p>
<input onChange={handleSubNameChange} />
</div>
)
}
useWindowWidth和useDocumentTitle、useFormInput是custom hook,因custom hook复用的是stateful logic,而不是state本身。另外custom hook必须以use开头来命名,这样linter工具才能正确检测其是否符合规范。
注意、注意、注意:
1、不能在条件语句、循环语句中使用钩子,它必须在顶层。
2、自定义钩子函数必须使用use开头