useState
使用状态:
const[n, setN] = React.useState(0)
const[user, setUser] = React.useState({name:'frank})
注意事项1:不可局部更新
如果 state 是一个对象,能否只更新部分?
答案是不行,示例代码
因为 setState 不会帮我们合并属性。
注意事项2:对象地址要变
setState (obj) 如果 obj 地址不变,即使数据变了,那么 React 认为 数据没有变化。
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)
// 正确y
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 接收函数
const [state, setState] = useState(()=>{return initialState})
该函数返回初始 state, 且只执行一次
setState 接收函数
setN(i => i + 1)
优先使用函数,demo
useReducer
用来践行 Flux/Redux 的思想
共四步:
- 创建初始值 initialState
- 创建所有操作 reduce(state, actioin)
- 传给 useReducer, 得到读和写 API
- 调用写 ({type: ‘操作类型’})
总的来说 useReducer 是 useState 的复杂版。
// 创建初始值
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() {
// useReducer
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
// 调用 写 ({type: ‘操作类型’})
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>
);
}
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
useContext
上下文
全局变量是全局的上下文
上下文是局部的全局变量
使用:
- 使用
C = createContext(initial)
创建上下文 - 使用
<C.provider>
圈定作用域 - 在作用域内使用
useContext(C)
来使用上下文
注意:
useContext 不是响应式的。
在一个模块将 C 里的值改变,另一个模块不会感知到这个变化。
useEffect
副作用
- 对环境的改变即为副作用,如修改 document.title
- 但我们不一定非要把副作用放在 useEffect 里
- 实际上叫做 afterRender 更好,因为每次 render 后会执行
用途:
- 作为 componentDidMount 使用, [] 作第二个参数
- 作为 componentDidUpdate 使用,可指定依赖
- 作为 componentWillUnmount 使用,通过 return
- 以上三种用途可同时存在
特点:如果同时存在多个 useEffect,会按照出现次序执行。
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
用法:
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。
拓展:
初始化:const count = ref(0)
读取:count.value
不同点:Vue3 会自动 render。
forwardRef
demo1:props 无法传递 ref 属性
demo2:实现 ref 的传递
demo3:两次 ref 传递得到 button 的引用
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} />;
};
// 实现 ref 的传递
const Button3 = forwardRef(Button2);
// const Button4 = forwardRef((props, ref) => {
// console.log(props);
// console.log(ref);
// return <button className="red" ref={ref} {...props} />;
// });
useRef:
- 可以用来引用 DOM 对象
- 也可以用来引用 普通对象
forwardRef:
- 由于 props 不包含 ref,所以需要 forwardRef
- 为生命 props 不包含 ref ?以为大部分时候不需要。
useImperativeHandle
用于自定义 ref 的属性。推荐阅读
// 子组件
const ChildComponent = forwardRef((props, ref) => {
const [count, setCount] = useState(0); //子组件定义内部变量count
//子组件定义内部函数 addCount
const addCount = () => {
setCount(count + 1);
};
//子组件通过useImperativeHandle函数,将addCount函数添加到父组件中的ref.current中
useImperativeHandle(ref, () => ({ addCount }));
return (
<div>
{count}
<button onClick={addCount}>child</button>
</div>
);
});
// 父组件
function Imperative() {
const childRef = useRef(null); //父组件定义一个对子组件的引用
const clickHandle = () => {
childRef.current.addCount(); //父组件调用子组件内部 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
过时闭包
参考文章链接