当前位置: 代码迷 >> 综合 >> React Hooks 入门上
  详细解决方案

React Hooks 入门上

热度:40   发布时间:2024-02-01 04:57:51.0

前面的话

实习开始没多久,就开始帮着做一个管理系统的项目, 项目技术战使用 react + ts + antd。在三者之前都没接触的情况下,我硬着头皮上了,class 组件都还没搞明白,就开始了 hooks 之旅。。。

1、Hooks

React Hooks 的意思是: 组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
React Hooks 就是那些钩子,需要什么功能,就使用什么功能。

2、注意事项

  • 只能在函数组件最外层调用 hooks, 不要在条件、循环、嵌套中调用 hooks
  • 不要在其他 JavaScript 函数中调用

3、useState

class 组件中有 state 状态,而函数组件中不存在。使用 useState 就可以为函数组件引入 state。

const [state, setState] = useState(initialState);
  • useState 的唯一参数是初始值 initialState。
  • useState 返回一个数组,一个是 state,另一个是改变 state 的函数。
  • 在初始渲染时,返回的 state 与初始的参数值一样。
  • 如果想改变这个state,可以通过调用这个函数来改变,不过这里要注意的是:与 class 组件的 this.setState不一样的是,它不会把新的 state 与旧的 state 进行合并,而是直接替换。
  • 调用状态更新函数后,React 重新渲染组件,以使新状态变为当前状态。

3.1 使用例子

interface Child1Props {num: number;setNum: Dispatch<SetStateAction<number>>;
}interface Child2Props {text: string;setText: Dispatch<SetStateAction<string>>;
}const Child1 = (props: Child1Props) => {const {num, setNum} = props;return (<div><button onClick={() => setNum(num + 1)}>{num}</button></div>);
};
const Child2 = (props: Child2Props) => {const {text} = props;return (<div><span>input: {text}</span></div>);
};const App = () => {const [num, setNum] = useState<number>(0);const [text, setText] = useState<string>('');return (<div><Child1 num={num} setNum={setNum} /><Child2 text={text} setText={setText} /><input type='text' value={text} onChange={e => setText(e.target.value)} /></div>);
};

3.2 使用回调更新状态

如果更新的状态需要依赖与前一个state状态,那么可以使用回调来更新状态:

setState(preState => nextState);

例子:

const App = () => {const [on, setOn] = useState(false);return (<><span>{on ? '开' : '关'}</span><button onClick={() => setOn(on => !on)}>{'on/off'}</button></>);
};

3.3 惰性的初始化

  • 初始的 state 只会在初始化渲染时,才会起作用,之后的渲染会被忽略。
  • 如果初始的 state 需要通过复杂的计算才能得到,那么可以传入一个函数,该函数只在初次渲染时执行,之后会被忽略。

例子:

someExpensiveComputation是一个相对耗时的操作,如果我们直接使用:

const initialState = someExpensiveComputation(props);
const [state, setState] = useState(initialState);

这样每次函数被渲染时,都会执行这个 someExpensiveComputation 耗时的操作。 如果使用惰性初始化的方法:

const [state, setState] = useState(()=>someExpensiveComputation(props));

这样只会在初次渲染时执行someExpensiveComputation这个函数,从而性能优化。

3.4 多个 useState 相互独立

假如一个函数组件有多个状态: 每次 useState 里面都只传一个值,并没有告诉没个值对应的 key 是哪一个,react 如何保证多个 useState 是相互独立的?

答案是: 根据 useState 的出现顺序来决定的。

const TabsBasic = () => {const [loading, setLoading] = useState<boolean>(false);const [name, setName] = useState<string>('xiaoqi');const [num, setNum] = useState<number>(1);// ...
}
第一次渲染:useState<boolean>(false); //  将 loading 值设置为 falseuseState<string>('xiaoqi'); // 将 name 设置为 xiaoqiuseState<number>(1);// 将 num 设置为 1第二次渲染:useState<boolean>(false); //  将 loading 值设置为读取的新值useState<string>('xiaoqi'); // 同上useState<number>(1);//  同上

如果将代码修改一下:

   const TabsBasic = () => {let  show = true;const [loading, setLoading] = useState<boolean>(false);if(show) {const [name, setName] = useState<string>('xiaoqi');show = false;}const [num, setNum] = useState<number>(1);// ...
}

再来看一下渲染情况:

第一次渲染:useState<boolean>(false); //  将 loading 值设置为 falseuseState<string>('xiaoqi'); // 将 name 设置为 xiaoqiuseState<number>(1);// 将 num 设置为 1第二次渲染:useState<boolean>(false); //  将 loading 值设置为读取的新值
//  useState<string>('xiaoqi'); useState<number>(1);//  读取的值是 name 的值,报错

所以前面说的必须把 hooks 放在最外层,不要在条件、循环、嵌套中调用 hooks。

3.5 性能优化

  • 与 class 组件不同的是,当传入的值没有发生变化时,组件不会重新渲染。
  • 与 class 组件不同的是,传入的值是直接替换,而不是合并,所以要使用扩张运算符。
const App = () => { const [counter, setCounter] = useState({name: '计数器', number: 0});return (<><p>{counter.name} : {counter.number}</p><buttononClick={() => setCounter({...counter, number: counter.number + 1})}>+</button>// 不会重新渲染<button onClick={() => setCounter({...counter})}>++</button></>);
};

4、useEffect

useEffect 用于处理副作用函数。

  • effect 副作用: 比如 ajax 请求、操作原生DOM 元素、绑定/解绑事件、添加订阅、设置定时器、本地持久化缓存等
  • useEffect 接受一个函数,该函数会在组件渲染到屏幕之后才执行,该函数要么返回一个清楚副作用的函数,要么就什么不返回。
  • useEffect 与 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount具有相同的用途
  • useEffect 在浏览器渲染完毕之后才会执行,所以不会阻塞视图的更新。

4.1 使用例子

const Counter = () => {const [count, setCount] = useState(0);useEffect(()=>{setTimtout(()=>{console.log(count)},3000)})return (<div><p>{count}</p><button onClick={() => setCount(count + 1)}>Click me</button></div>)
}

每次重新渲染组件时,都会执行一次 useEffect 函数。

4.2 清除副作用

在 useEffect 中返回一个函数,用于清除副作用,这个函数会在组件卸载之前执行。

const Counter = () => {const [num, setNum] = useState<number>(0);const [text, setText] = useState<string>('');useEffect(() => {let timer = setInterval(() => {setNum(num => num + 1);}, 1000);return () => {console.log('clear');clearInterval(timer);};});return (<><inputtype='text'value={text}onChange={event => setText(event.target.value)}/><p>{num}</p><button>+</button></>);
};

在每一次setNum 之后组件都会重新渲染,每次重新渲染之前都会清除定时器。

跳过不必要的副作用

如果一个副作用函数只想再初次渲染时执行,可以给useEffect 传第二个参数。用第二个参数来告诉react 只有当这个依赖值发生变化时,才执行副作用函数。当第二个参数为[]时,表示只在初次渲染时执行。

// 初次渲染时才执行
useEffect(() => {document.title = `You clicked ${count} times`;
}, []);  
// 只有当count的值发生变化时,才会重新执行
useEffect(() => {document.title = `You clicked ${count} times`;
}, [count]);
  相关解决方案