官方文档
useState
使用状态:
const[n, setN] = React.useState(0)
const[user, setUser] = React.useState({name:'frank})
注意事项1:不可局部更新
如果 state 是一个对象,能否只更新部分?
答案是不行,示例代码
因为 setState 不会帮我们合并属性。
注意事项2:对象地址要变
setState (obj) 如果 obj 地址不变,即使数据变了,那么 React 认为 数据没有变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, {useState} from "react"; import ReactDOM from "react-dom"; function App() { const [user,setUser] = useState({name:'Frank', age: 18}) const onClick = ()=>{ user.name = 'java' setUser(user) setUser({ ...user, name: 'java' }) } return ( <div className="App"> <h1>{user.name}</h1> <h2>{user.age}</h2> <button onClick={onClick}>Click</button> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
|
useState 接收函数
1 2
| const [state, setState] = useState(()=>{return initialState}) 该函数返回初始 state, 且只执行一次
|
setState 接收函数
优先使用函数,demo
自定义 useState
useReducer
用来践行 Flux/Redux 的思想
共四步:
- 创建初始值 initialState
- 创建所有操作 reduce(state, actioin)
- 传给 useReducer, 得到读和写 API
- 调用写 ({type: ‘操作类型’})
总的来说 useReducer 是 useState 的复杂版。
demo
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 32 33 34
| const initial = { n: 0 };
const reducer = (state, action) => { if (action.type === "add") { return { n: state.n + action.number }; } else if (action.type === "multi") { return { n: state.n * 2 }; } else { throw new Error("unknown type"); } }; function App() { const [state, dispatch] = useReducer(reducer, initial); const { n } = state; const onClick = () => { dispatch({ type: "add", number: 1 }); }; const onClick2 = () => { dispatch({ type: "add", number: 2 }); }; return ( <div className="App"> <h1>n: {n}</h1>
<button onClick={onClick}>+1</button> <button onClick={onClick2}>+2</button> </div> ); }
|
useReducer 表单例子
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import React, { useReducer } from "react"; import ReactDOM from "react-dom";
const initFormData = { name: "", age: 18, nationality: "汉族", };
const reducer = (state, action) => { switch (action.type) { case "patch": return { ...state, ...action.formData }; case "reset": return initFormData; default: throw new Error(); } };
const App = () => { console.log("aaa"); const [formData, dispatch] = useReducer(reducer, initFormData); const onSubmit = () => {}; const onReset = () => { dispatch({ type: "reset" }); }; return ( <form onSubmit={onSubmit} onReset={onReset}> <div> <label> 姓名 <input value={formData.name} onChange={(e) => dispatch({ type: "patch", formData: { name: e.target.value }, }) } /> </label> </div> <div> <label> 年龄 <input value={formData.age} onChange={(e) => dispatch({ type: "reset", formData: { age: e.target.name }, }) } /> </label> </div> <div> <label> 民族 <input value={formData.nationality} onChange={(e) => dispatch({ type: "patch", formData: { nationality: e.target.value }, }) } /> </label> </div> <div> <button type="submit">提交</button> <button type="reset">重置</button> </div> <hr /> {JSON.stringify(formData)} </form> ); };
ReactDOM.render(<App />, document.getElementById("root"));
|
如何代替 Redux
步骤:
- 将数据集中在一个 store 对象
- 将所有操作集中在 reducer
- 创建一个 Context
- 创建对数据的读写 API
- 将第四步的内容放到第三步的 Context
- 用 Context.Provider 将 Context 提供给所有组件
- 各个组件用 useContext 获取读写 API
demo ,模块化后 demo
useContext
上下文
全局变量是全局的上下文
上下文是局部的全局变量
使用:
- 使用
C = createContext(initial)
创建上下文
- 使用
<C.provider>
圈定作用域
- 在作用域内使用
useContext(C)
来使用上下文
demo
注意:
useContext 不是响应式的。
在一个模块将 C 里的值改变,另一个模块不会感知到这个变化。
useEffect
副作用
- 对环境的改变即为副作用,如修改 document.title
- 但我们不一定非要把副作用放在 useEffect 里
- 实际上叫做 afterRender 更好,因为每次 render 后会执行
用途:
- 作为 componentDidMount 使用, [] 作第二个参数
- 作为 componentDidUpdate 使用,可指定依赖
- 作为 componentWillUnmount 使用,通过 return
- 以上三种用途可同时存在
特点:如果同时存在多个 useEffect,会按照出现次序执行。
demo
useLayoutEffect
布局副作用
useEffect 在浏览器渲染完成后执行,demo
useLayoutEffect 在浏览器渲染前执行,通过时间点来侧面证明
特点:
useLayoutEffect 总是比 useEffect 先执行,demo
useLayoutEffect 里的任务最好影响了 Layout,不然没必要用 useLayoutEffect。
经验:
为了用户体验,优先使用 useEffect(优先渲染)
useMemo
要理解 React.useMemo,需要先了解 React.memo,React 默认有多余的 render,demo,代码中的 Child 用 React.memo(Child)代替,如果 props 不变,就不会再次执行函数组件。
useMemo 的作用就是提供了一个 memorize 值,在依赖项改变之后,该值才会改变才会被重新计算。demo
特点:
第一个参数是 ()=>value
第二个参数是依赖 [m, n]
只有当依赖变化时,才会计算出新的 value,如果依赖不变,那么就重用之前的 value。类似于 vue2 的 computed。
注意:如果 value 是个函数,那么就要写成 useMemo( ()=> (x)=>console.log('1') )
,这是一个返回函数的函数,
于是就有了 useCallback,useCallback( (x)=>console.log('1') )
useCallback
用法:
1 2 3
| useCallback(x => console.log(x), [m]) 等价于 useMemo( () => x=>console.log(x), [m])
|
useRef
目的:
如果你需要一个值,在组件不断 render 时保持不变
初始化:const count = useRef(0)
,code
读取:count.current
为什么需要 current ?
为了保证两次 useRef 是同一个值(只有引用能做到)
useRef 不会自动更新 UI。
拓展:
vue3 的 ref,
初始化:const count = ref(0)
读取:count.value
不同点:Vue3 会自动 render。
forwardRef
demo1:props 无法传递 ref 属性
demo2:实现 ref 的传递
demo3:两次 ref 传递得到 button 的引用
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
| const App = () => { const buttonRef = useRef(null); console.log("buttonRef", buttonRef); return ( <div> <h1>React.forwardRef</h1> <hr /> <div> <Button3 ref={buttonRef}>button</Button3> </div> </div> ); }; const Button2 = (props, ref) => { console.log(props); console.log(ref); return <button className="red" ref={ref} {...props} />; };
const Button3 = forwardRef(Button2);
|
useRef:
- 可以用来引用 DOM 对象
- 也可以用来引用 普通对象
forwardRef:
- 由于 props 不包含 ref,所以需要 forwardRef
- 为生命 props 不包含 ref ?以为大部分时候不需要。
useImperativeHandle
用于自定义 ref 的属性。推荐阅读
demo1 使用 useImperativeHandle
demo2 不使用 useImperativeHandle
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 32 33 34 35 36 37 38
| const ChildComponent = forwardRef((props, ref) => { const [count, setCount] = useState(0); const addCount = () => { setCount(count + 1); }; useImperativeHandle(ref, () => ({ addCount })); return ( <div> {count} <button onClick={addCount}>child</button> </div> ); });
function Imperative() { const childRef = useRef(null);
const clickHandle = () => { childRef.current.addCount(); };
return ( <div> {/* 父组件通过给子组件添加 ref 属性,将childRef传递给子组件, 子组件获得该引用即可将内部函数添加到childRef中 */} <ChildComponent ref={childRef} /> <button onClick={clickHandle}>child component do something</button> </div> ); }
function App() { return <Imperative />; }
|
自定义 Hook
封装数据操作
简单例子,贴心例子
还可以在自定义 Hook 里使用 Context
useState 只说了不能在 if 里,没说不能在函数里运行,只要这个函数在函数组件里运行即可。
Stale Closure
过时闭包
参考文章链接