1 useState
-
使用状态
const [n, setN] = React.useState(0) const [user,setUser] = React.useState({name:'frank'}) -
注意事项1:不可局部更新
如果是一个对象,不能部分setState,因为setState会帮我们合并属性
import React, {useState} from "react" import ReactDom from "react-dom" function App() { const [user, setUser] = useState({name: 'f', age: 18}) const onClick = () => { setUser({ ...user, // 把user的所有的属性拷贝过来 name: 'jack'// 用name: 'jack' 覆盖之前的name }) } 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) -
注意事项:地址要变
setState(obj)如果obj地址不变,那么React就认为数据没有变化
const onClick = () => { setUser({ user.name = 'jack' setUser(user) //user的地址没变,UI不会更新 } -
useState接受函数
好处是减少多余的计算过程(用得少)
const [user, setUser] = useState(() => ({name: 'frank', age: 18})) -
setState接受函数
import React, {useState} from "react" import ReactDom from "react-dom" function App() { const [n,setN] = useState(0) const onClick = () => { // setN(n + 1) // setN(n + 1) // 这样写只会+1,因为setN不会直接改变n的值,要写成函数 setN(i => i +1) setN(i => i +1) } return ( <div className="App"> <h1>n : {n}</h1> <button onClick={onClick}>+2</button> </div> ) } const rootElement = document.getElementById("root") ReactDom.render(<App/>, rootElement)
2 useReducer
-
用来践行Flux/Redux的思想
四步走
- 创建初始值initialState
- 创建所有操作reducer(state, action)
- 传给useReducer,得到读和写的API
- 调用写({type : '操作类型'})
-
例子
import React, {useReducer} from "react" import ReactDom from "react-dom" const initialState = { 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, initialState) const {n} = state // 得到一个读API state 和一个写API dispatch 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> ) } const rootElement = document.getElementById("root") ReactDom.render(<App/>, rootElement)
3 useContext
-
上下文(运行一个程序所要知道的所有其他变量)
- 全局变量是全局的上下文
- 上下文是局部的全局变量
-
使用方法
- 使用C = createContext(initial)创建上下文
- 使用<C.provider>圈定作用域
- 在作用域内使用useContext(C)来使用上下文
-
例子
import React, {useState, createContext, useContext} from "react" import ReactDom from "react-dom" // 1. const C = createContext(null) function App() { const [n, setN] = useState(0) return ( // 2.使用<C.provider>圈定作用域 value的值 读接口和写接口 // n和setN可以共享给子代的任何组件 <C.Provider value={{n, setN}}> <div className="App"> <Farther/> </div> </C.Provider> ) } function Farther() { return ( <div>我是爸爸 <Child/></div> ) } function Child() { const {n, setN} = useContext(C) const onClick = () => { setN(i => i + 1) } return ( <div> 我是儿子,我得到的n : {n} <button onClick={onClick}>+1</button> </div> ) } const rootElement = document.getElementById("root") ReactDom.render(<App/>, rootElement) -
注意事项
不是响应式的,你在一个模块将C里面的值改变,另一个模块不会感知到这个变化
4 useEffect
-
副作用 对环境的改变即为副作用,如修改document.title,但我们不一定非要把副作用放在useEffect里,实际上叫afterRender更好,每次render后调用的函数
-
用途
-
作为componentDidMount使用,[]作为第二个参数
-
作为componentDidUpdate使用,可指定依赖
-
作为componentWillUnmount使用,通过return
以上三种用途可同时存在
-
-
特点
如果同时存在多个useEffect,会按照出现次序执行
-
例子
import React, {useState, useEffect} from "react" import ReactDom from "react-dom" function App() { const [n, setN] = useState(0) const onClick = () => { setN(i => i + 1) } useEffect(() => { console.log('第一次渲染后执行这句话') }, []) // 里面的变量变化时再次执行 => 不会再次执行 useEffect(() => { console.log('每次渲染后执行这句话') }) // 任何一个变量变化时都会执行 useEffect(() => { if (n !== 0) { console.log('n变化了') } }, [n]) // n变化时执行 return ( <div> n : {n} <button onClick={onClick}>+1</button> </div> ) } const rootElement = document.getElementById("root") ReactDom.render(<App/>, rootElement)
5 useLayoutEffect
-
布局副作用
- useEffect在浏览器渲染完成后执行
- useLayoutEffect在浏览器渲染前执行
-
特点
- useLayoutEffect总是比useEffect先执行
- useLayoutEffect里的任务最好影响了Layout
-
经验
为了用户体验,优先使用useEffect
6 useMemo
-
例子:多余的render
import React from "react"; import ReactDOM from "react-dom"; function App() { const [n, setN] = React.useState(0); const [m, setM] = React.useState(0); const onClick = () => { setN(n + 1); }; return ( <div className="App"> <div> <button onClick={onClick}>update n {n}</button> </div> <Child data={m}/> {/* <Child2 data={m}/> */} </div> ); } function Child(props) { console.log("child 执行了"); console.log('假设这里有大量代码') return <div>child: {props.data}</div>; } const Child2 = React.memo(Child); const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);效果:点击按钮更新n,子组件中的代码也会执行
-
将上面例子使用memo改写
const Child = React.memo(props => { console.log("child 执行了"); console.log('假设这里有大量代码') return <div>child: {props.data}</div>; }) -
但是react.memo有bug,在添加了监听函数之后,虽然新旧函数功能一样,但是地址不一样,组件重新渲染后函数会生成新的地址
import React from "react"; import ReactDOM from "react-dom"; function App() { const [n, setN] = React.useState(0); const [m, setM] = React.useState(0); const onClick = () => { setN(n + 1); }; const onClickChild = () => {} // 由于APP()重新执行了,所以这句话重新执行,函数的地址变了 return ( <div className="App"> <div> <button onClick={onClick}>update n {n}</button> </div> <Child data={m} onClick={onClickChild}/> </div> ); } const Child = React.memo(props => { console.log("child 执行了"); console.log('假设这里有大量代码') return <div onClick={props.onClick}>child: {props.data}</div>; }) const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement); -
用useMemo改写可解决
const onClickChild = useMemo(() => { return () => { } }, [m]) -
特点
- 第一个参数是
()=> value,第二个参数是依赖[n,m] - 只有当依赖变化时,才会计算出新的value,如果依赖不变,那么就要重用之前的value
- 第一个参数是
-
注意
如果你的value是个函数,那么你就要写成
useMemo(() => (x) => console.log(x))这是一个返回函数的函数,很难用,于是就有了
useCallBack -
useCallBack
用法
useCallback(x => log(x), [m])等价于useMemo(() => x => log(x), [m])
7 useRef
-
目的
如果需要一个值,在组件不断render时保持不变
-
使用
-
初始化:
const count = useRef(0) -
读取:
count.current为什么需要current?为了保证两次useRef是同一个值(只有引用才能做到)
-
-
forwardRef
由于props不包含ref,所以需要forwardRef