react
hook这里主要讲 hook 的语法和使用场景css
Hook 是一个特殊的函数,使用了 JavaScript 的闭包机制,可让你在函数组件里“钩入” React state 及生命周期等特性。Hook 不能在 class 组件中使用。这也就是我开篇说的函数式组件一把索的缘由html
Hook 的调用顺序在每次渲染中都是相同的,因此它可以正常工做,只要 Hook 的调用顺序在屡次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但若是咱们将一个 Hook 调用放到一个条件语句中会发生什么呢?vue
答案:Hook 的调用顺序发生了改变出现 bug Hook 规则react
是容许你在 React 函数组件中数据变化能够异步响应式更新页面 UI 状态的 hook。git
userState 函数初始化变量值,返回一个数组,数组第一项是这个初始化的变量,第二项是响应式修改这个变量的方法名。github
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。能够声明不少个
const [count, setCount] = useState<number>(0); // 数组解构,在typescript中使用,咱们能够用以下的方式声明状态的类型
const [fruit, setFruit] = useState<string>('banana');
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>// 修改 count 的值
Click me
</button>
</div>
);
}
复制代码
userState 的返回的第二个参数能够接受一个函数,若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。注意了 useState 不会自动合并更新对象,因此运算符来达到合并更新对象的效果。vuex
function Box() {
const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
useEffect(() => {
function handleWindowMouseMove(e) {
// 展开 「...state」 以确保咱们没有 「丢失」 width 和 height
setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
}
});// 没有第二个参数,只会渲染一次,永远不会重复执行
}
复制代码
通常状况下,咱们使用 userState hook,给他传的是一个简单值,可是若是初始 state 须要经过复杂计算得到,则能够传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用typescript
const [state, setState] = useState(() => {
return doSomething(props);
});
复制代码
useState 返回的更新状态方法是异步的,下一个事件循环周期执行时,状态才是最新的值。不要试图在更改状态以后立马获取状态。这里有可能会出现陈旧值引用的问题,这并非 reatc 的 bug,是由于 JavaScript 的正常表现,是由于闭包api
好比使用 immutable.js 里面的 set 结构的时候,进行循环删除里面的某些项,结果删除的永远是数组的最后一项
infos.forEach((el) => {
if( list.has(el.id)){
setList(list.delete(item.id))// 这里是异步,在你循环的时候,页面尚未重绘,拿不到最后一个值
}
})
复制代码
若是咱们想要实现循环里面删除,那么怎么作呢?别忘了,useState 是想要咱们直接修改 DOM 的渲染,因此才使用他的。咱们能够先总体的修改完以后再去影响 DOM 的渲染
infos.forEach((el) => {
if (list.has(el.id)) {
list = list.delete(el.id)//这里是同步删除
}
})
setList(list)//删除完了以后,在去修改DOM的结构
复制代码
React 这样设计的目的是为了性能考虑,争取把全部状态改变后只重绘一次就能解决更新问题,而不是改一次重绘一次,也是很容易理解的.内部是经过 merge 操做将新状态和老状态合并后,从新返回一个新的状态对象,组件中出现 setTimeout 等闭包时,尽可能在闭包内部引用 ref 而不是 state,不然容易出现读取到旧值的状况.闭包引用的是原来的旧值,一旦通过 setUsetate,引用的就是一个新的对象,和原来的对象引用的地址不同了。
可以直接影响 DOM 的变量,这样咱们才会将其称之为状态。当某一个变量对于 DOM 而言没有影响,此时将他定义为一个异步变量并不明智。好的方式是将其定义为一个同步变量。
useState 的替代方案,升级版,但咱们遇到多个 useState 之间互相影响,须要或者说只是某一个参数不同,其余的大体差很少的时候,咱们就可使用 useReducer 替换,这个有点像 vue 里面的 vuex 的感受,也有点 Redux 的感受,可是只是有一点点,几个仍是彻底不同的概念
const initialState = {count: 0};
// 多个 useState 结合成一个了
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
复制代码
The State Reducer Pattern with React Hooks
React 会等待浏览器完成画面渲染以后才会延迟调用 useEffect,他至关于 react class 的三个生命周期函数 componentDidMount(组件挂载完成),componentDidUpdate(组件更新) 和 componentWillUnmount(组件将要销毁) 三个生命周期函数的组合,能够实现减小重复代码的编写
componentDidMount: 组件挂载完成的时候,须要执行一堆东西
componentDidUpdate:组件更新钩子函数,就理解成 vue 里面的 watch 吧,当你监听的某一个数据发生变化的时候,就会执行这一个 Effect Hook 钩子函数里面的东西。
componentWillUnmount:清除 effect ,在某种状况下,你须要清理一些数据为了不内存泄露的时候就能够用它。 返回一个函数,就表示你要作的清空操做了。不返回一个函数就表示不须要作清空操做。(组件卸载,
const [debounceVal, setDebounceVal] = useState(value)
useEffect(() => {
const handle = setTimeout(() => {
setDebounceVal(value)
}, delay)
return () => {
clearTimeout(handle) // 组件销毁的时候清空定时器
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return debounceVal
复制代码
默认状况下,它在第一次渲染以后和每次更新以后都会执行,并且 effect 的清除阶段在每次从新渲染时都会执行,这个能就会致使性能问题 ,因此他又称是反作用。他能够接受第二个参数,他会对比更新先后的两个数据,若是没有变化的话,就不执行 hook 里面的东西。仅仅只有在第二次参数发生变化的时候才会执行。这样就避免没有必要的重复渲染和清除操做
能够传递一个空数组([])做为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,因此它永远都不须要重复执行。意味着该 hook 只在组件挂载时运行一次,并不是从新渲染时,(须要注意的是[]是一个引用类型的值,在某些状况下自定义 hooks,他做为第二个参数也会致使页面从新渲染,由于引用地址变了,因此在自定义 hooks 的时候须要注意,在自定义 hook 详细说
useEffect 完整指南 -> 这个写的特别好,特别推荐看学习
简单说就是把一些须要计算可是不会变得数据存储在本地,下次用的时候直接拿计算的结果就行了,不须要计算( 若是咱们有 CPU
密集型操做,咱们能够经过将初始操做的结果存储在缓存中来优化使用。若是操做必然会再次执行,咱们将再也不麻烦再次使用咱们的 CPU
,由于相同结果的结果存储在某个地方,咱们只是简单地返回结果他经过内存来提高速度,React.useMemo
是新出来的 hooks api
,而且这个 api
是做用于 function
组件,此方法仅做为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,由于这会产生 bug。
把“建立”函数和依赖项数组做为参数传入 useMemo,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
function App() {
const [num, setNum] = useState(0);
// 一个很是耗时的一个计算函数
// result 最后返回的值是 49995000
function expensiveFn() {
let result = 0;
for (let i = 0; i < 10000; i++) {
result += i;
}
console.log(result) // 49995000
return result;
}
const base = expensiveFn();
// const base = useMemo(expensiveFn, []); 只有在第一次点击的时候才会执行,后来都不执行了,他的第二个参数和useEffect同样的意思
return (
<div className="App">
<h1>count:{num}</h1>
<button onClick={() => setNum(num + base)}>+1</button>
</div>
);
}
复制代码
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操做,诸如反作用这类的操做属于 useEffect 的适用范畴,而不是 useMemo
父组件给子组件传递函数的时候,父组件每一次的修改都会从新渲染,都会致使它们在每次渲染上都有不一样的引用,最后的结果是,每一次父组件的修改都直接致使了子组件没有必要的渲染。(引用类型
这个时候咱们吧把函数以及依赖项做为参数传入 useCallback,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新。
给定相同 props 的状况下渲染相同的结果,而且经过记忆组件渲染结果的方式来提升组件的性能表现,第二个参数表明的意义和上面的同样
// 避免引用类型的重复渲染
const handleIndicator = useCallback((indicator: EvaluateIndicator) => {
console.log('传给字组件')
}, [])
复制代码
// 函数防抖
import React, { useState, useCallback } from 'react'
import { debounce } from '../../utils/tool'
import './index.scss'
interface searchlParams {
handleSearch: (val: string) => void
}
const Search: React.FC<searchlParams> = ({ handleSearch }) => {
const [value, setValue] = useState<string>('')
/* 防抖的另一种写法 */
const debounceSearch = useCallback(
debounce((val) => handleSearch(val), 2000),
[],
)
const changhandleSearch = (e: any) => {
setValue(e.target.value)
debounceSearch(e.target.value)
}
return (
<div className="search-wrapper">
<input className="input-control" value={value} onChange={changhandleSearch} placeholder="搜索" />
</div>
)
}
复制代码
子组件须要配合 React.memo 的使用,React.memo 和 useCallback 都是为了减小从新 render 的次数
useCallback 和 useMemo 均可缓存函数的引用或值,可是从更细的使用角度来讲 useCallback 缓存函数的引用,useMemo 缓存计算数据的值
能够减小从新 render 的次数的。
//子组件
function Child(props) {
console.log(props.name)
return <h1>{props.name}</h1>
}
export default React.memo(Child)
// 父组件
function App() {
const [count, setCount] = useState<number>(1)
return (
<div className="App">
<h1>{ count }</h1>
<button onClick={() => setCount(count+1)}>改变数字</button>
<Child name="sunseekers"></Child>
</div>
);
}
复制代码
若是你的函数组件在给定相同 props 的状况下渲染相同的结果,那么你能够经过将其包装在 React.memo 中调用,以此经过记忆组件渲染结果的方式来提升组件的性能表现。这意味着在这种状况下,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。(若是没有用 React.memo 包裹,每一次 count 变化,子组件都会从新渲染)
仅检查 props 变动。若是函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会从新渲染.默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现
至关于 vue 里面的 refs ,只是在这边的用法不同而已。useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变,当咱们遇到了由于闭包问题致使的陈旧值引用的问题,咱们就能够用它来解决问题
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
复制代码
在更新过程当中它会被执行两次,第一次传入参数 null,而后第二次会传入参数 DOM 元素,因此在控制太能够打印两条数据信息出来
The State Reducer Pattern with React Hooks
这个有就有点像 vue 里面的 mixin 了,当咱们在多个组件函数里面共同使用同一段代码,而且这段代码里面包含了 react 的 hook,咱们想在多个组件函数共享逻辑的时候,咱们能够把他提取到第三个函数中去,而组件和 Hook 都是函数,因此也一样适用这种方式。
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部能够调用其余的 Hook,在两个组件中使用相同的 Hook 不会共享 state,是独立的 state
import { useState, useCallback, useEffect } from 'react'
export function useFriendStatus(fn, dependencies) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
// 请求的方法 这个方法会自动管理loading
const request = useCallback(() => {
setLoading(true)
setData(fn)
setLoading(false)
})
// 根据传入的依赖项来执行请求
useEffect(() => {
request()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dependencies])
return {
// 请求获取的数据
data,
// loading状态
loading,
// 请求的方法封装
request,
}
}
// 组件中使用
const { data, loading } = useFriendStatus(fetchTodos({ tab: 'activeTab' }), 'activeTab')
复制代码
若是 dependencies 是引用类型的要注意了,会致使每一次加载页面引用的地址都不同,直接致使页面死循环,因此处理的时候, 要特别当心和注意了。好比说,若是咱们给 useFriendStatus 第二个参数一个空数组,每一次请求接口页面就会从新渲染,第二个参数的空数组引用地址变了,会致使死循环,本身尝试
//@ts-ignore
import React, { useState, useEffect } from 'react'
export default function useDebounce(value, delay) {
const [debounceVal, setDebounceVal] = useState(value)
useEffect(() => {
const handle = setTimeout(() => {
setDebounceVal(value)
}, delay)
return () => {
clearTimeout(handle)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return debounceVal
}
// 组件中使用
interface searchlParams {
handleSearch: (val: string) => void
}
const Search = ({ handleSearch:searchlParams }) => {
const [value, setValue] = useState<string>('')
// 函数防抖,每一次内部变量变化都会注册和执行setTimeout,函数从新渲染以后
const debounceSearch = useDebounce(value, 2000)
useEffect(() => {
handleSearch(value)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debounceSearch])
return (
<div className="search-wrapper">
<input className="input-control" value={value} onChange={(e) => setValue(e.target.value)} placeholder="搜索" />
{/* <i className="iconfont ico search-ico"></i> */}
</div>
)
}
复制代码