Please enable Javascript to view the contents

学习React_hooks_1

 ·  ☕ 4 分钟

React 理念:UI = f(data)
纯函数在加载不同数据后产生了不同的 UI

useState

1
2
3
4
const [n, setN] = useState(0)
console.log(n) //读
onClick = () => setN(n + 1) //写
onClick2 = () => setN((i) => i + 1) //也支持传入一个匿名函数

随着组件更新 render,useState 会产生多个 n,但每次 get 的 n 是最新的 n

组件每次重新渲染,组件里的函数都会重新执行,
对应的所有 state 都会出现重复分身
如果你不希望出现重复分身的变量
可以用 useRef/useContext 等
如:

1
2
3
const nRef = useRef(0)
console.log(nRef.current)
onClick = () => (nRef.current += 1)

useState 不能局部更新
useState 如果数据地址没变那么 react 就不会重新 render 更新

如果 useState 的对象生成复杂建议把初始值写成一个匿名函数,不然每次重新渲染时都要做多余的计算开销
useState(() => ({ name: ’neo' }))

setState 会合并多个同级别的对 n 的操作,
如:

1
2
3
4
setN(n + 1)
setN(n + 1)
setN(n + 1)
//这段代码只会执行会后一行的setN(n+1)
1
2
3
4
setN(n + 1)
setN(n + 2)
setN(n + 3)
//同上也只会执行最后的setN(n+3)

所以 setState 建议传入一个匿名函数方法,可以防止上面的合并操作,也可以防止代码出现过时的闭包,
比如: setN(i => i + 1)


useReducer

useReducer 是一个把对 state 的所有操作都聚拢在内的 useState
用来践行 flux 思想,具体过程如下:
1.创建初始值 initialState
2.创建所有操作 reducer(state, action)
3.传给 useReducer,得到读和写 api
4.调用时写 ({ type: ‘操作类型' })

redux 主要做到了:
1.集中管理全局状态和对状态的操作
2.提供所有子组件可以在上下文中读写全局状态

useContext

现在可以用 useState/useReducer 和 useContext 来代替 redux

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const AppContext = React.createContext(null)

//可以用useState生成读写
const [name, setName] = useState('the_one');

//也可以用useReducer来生成读写
const initialState = {age:0, sex:'male'}
const reducer = (prevState, action) => {
  swicth (action.type) {
    case 'age':
      return {
        ...prevState,
        age: action.payload
      }
    case 'sex':
      return {
        ...prevState,
        sex: action.payload
      }
    default:
      throw new Error('error action type')
      break;
  }
}
const [ personInfo, dispatch ] = useReducer(reducer, initialState)

//用AppContext.Provider来包裹,在value中传入要全局使用的读写name操作
<AppContext.Provider value={{name, setName, personInfo, dispatch}}>
  <CompontentA/>
  <CompontentB/>
</AppContext.Provider>
1
2
3
4
5
6
//在组件A和组件B中就可以获取context中的vaule
//const {name, setName, personInfo, dispatch} = useContext(AppContext)

setName('Neo')
dispatch({ type: 'age', payload: '25' })
console.log(name, personInfo)

useEffect

useEffect(定义:执行在 render 后的副作用)
依赖参数中数组里的值的变化来决定是否要执行函数

App() -> 执行 -> VDOM(VNode)
-> DOM ->useLayoutEffect -> 浏览器改变外观 -> useEffect

useEffect 在浏览器渲染完成后执行
useLayoutEffect 在浏览器渲染前执行
useLayoutEffect 总是比 useEffect 先执行,
useLayoutEffect 里的任务最好是影响了 useEffect


useMemo && useCallBack

useMemo 可以实现函数的重用,防止在多次 render 的时候做不必要的重复创建和执行函数
第一个参数是 ()=>value
第二个参数是依赖 [m,n]
只有当依赖不变时,才会计算新的 value,
如果依赖不变,那么就重用之前的 value
(vue2 computed?)

如果 value 是一个函数,那么要写成
()=> ((x)=> console.log(x))
可以用 useCallBack 简化
useCallback(x=>log(x), [m]) 等价于
useMemo(() => x => log(x), [m])


useRef

useRef 的目的:在组件不断 render 时,需要一个值保持不变,比如可以累积计算之类的
useRef 原理:用对象的形式来存变量,在对象里可以做到改变对象的值,而不变对象的地址
为了保证值不变,所以使用引用

初始化:const count = useRef(0)
读取:count.current


汇总

App() 重复 rendering…
每次变 useState/useRender,setN 会触发重新 render
有条件时变 useMemo/useCallBack,防止函数有多余的 render
不变 useRef ,改变值不会触发重新 render
(vue3 ref 值有变化会自动 render)


React.forwardRef

我们可以给一个函数组件一个 ref 来方便引用一个组件(就可以不用 getElementById 了),
但是函数式组件不能直接用 useRef 产生的 ref(class 组件可以直接用),因为函数式组件的 props 里不包含 ref
此时需要用 React.forwardRef 包裹一下函数组件来获得 ref

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function App() {
    const buttonRef = useRef(null);
    return (
        <div className="app">
            <Button2 ref={buttonRef}>按钮</Button2>
        </div>
    )
}

const Button2 = React.forwardRef((props, ref) => {
    return <button className="red" ref={ref} {props} />
})

useImpreativeHandle

useImpreativeHandle 可以以 ref 为参数返回一个自定义的 ref 对象,封装 ref 对象,隐藏一些属性。

自定义 hooks

自定义 hooks 是 对已有的基础 react hooks 互相组合的封装


记录日常遇到的问题: 1.表单像 input 是用 useState 还是 useRef,有什么区别?

如果将表单值保持在状态(即受控组件),一个好处是可以在更改时立即访问值,例如,如果你希望在用户在输入框中键入某些内容时禁用某些按钮。
如果采用 ref 方法(也称为非受控组件),一个好处是不必每次用户键入时都重新呈现组件(因为调用 state update 会重新呈现)

分享

Llane00
作者
Llane00
Web Developer