useState
import React from 'react'
export default function App() {
const [n, setN] = React.useState(0)
const addN = () => {
setN(n+1)
}
return (
<div className="App">
<div>{n}</div>
<button onClick={addN}>+1</button>
</div>
);
}
注意事项
setState不会自动合并属性
const [user, setUser] = React.useState({name: 'jack', age: 16})
setUser({...user, age: 19})
如果state是对象,setState应返回一个新对象,如果obj地址不变, React会认为数据没变化
useState接受函数,好处是减少浏览器解析
const [state, setState] = React.useState(()=>{return initialState})
setState接受函数,setState更新是异步的,传回调函数保证每次setState都能更新
setState(n => n + 1)
useReducer
useReducer践行Flux/Redux思想 ```javascript import React from ‘react’ // 1. 创建初始值initialState const initial = { n: 0 } // 2. 创建所有操作reducer(state, action) const reducer = (state, action) => { if (action.type === ‘add’) { return {n: state.n + action.number} } else if(action.type === ‘multi’) { return {n: state.n * action.number} } else { throw new Error(“unknown type”) } }
export default function App() { // 3. 将reducer和initialState传给useReducer, 得到读和写API const [state, dispatch] = React.useReducer(reducer, initial)
const addN = ()=>{ // 4. 调用写API dispatch({type: ‘ad’, number: 2}) }
return (
<a name="6AESe"></a>
### useReducer替代Redux
```javascript
import React, { useEffect } from "react";
// 1. 初始化store
const store = {
user: null,
books: null,
movies: null
};
// 2. 将所有操作集中在reducer
function reducer(state, action) {
switch (action.type) {
case "setUser":
return { ...state, user: action.user };
case "setBooks":
return { ...state, books: action.books };
case "setMovies":
return { ...state, movies: action.movies };
default:
throw new Error();
}
}
// 3. 创建Context
const Context = React.createContext(null);
export default function App() {
// 4. 用useState创建读和写的API
const [state, dispatch] = React.useReducer(reducer, store);
const api = { state, dispatch };
return (
// 5. 通过Context.Provider将第四步的内容传给各组件
<Context.Provider value={api}>
<User />
<hr />
<Books />
<Movies />
</Context.Provider>
);
}
function User() {
// 6. 各组件通过useContext获取读写API
const { state, dispatch } = React.useContext(Context);
useEffect(() => {
ajax("/user").then((user) => {
dispatch({ type: "setUser", user: user.name });
});
}, []);
return (
<div>
<h1>个人信息</h1>
<div>name: {state.user ? state.user : ""}</div>
</div>
);
}
完整代码:https://codesandbox.io/s/sweet-feather-iglyi
组件模块化
以上例为例
- 将组件都放到components文件夹里
- 将ajax和Context都放到单独的文件
- 将reducer都放到reducers文件夹
export default {
setUser: (state, action) => {
return { ...state, user: action.user };
}
};
const obj = {
...userReducer,
...booksReducer,
...moviesReducer
};
function reducer(state, action) {
const fn = obj[action.type];
if (fn) {
return fn(state, action);
} else {
throw new Error();
}
}
完整代码:https://codesandbox.io/s/beautiful-neumann-ko4n5
useContext
Context是局部的全局变量
import React from "react";
function Papa() {
return (
<div>
{" "}
我是Papa
<Son />
</div>
);
}
function Son() {
// 3. 通过useContext获得数据
const { n, setN } = React.useContext(Context);
return (
<div>
我是Son
<div>{n}</div>
<button
onClick={() => {
setN(n + 1);
}}
>
+1
</button>
</div>
);
}
// 1. 创建一个Context
const Context = React.createContext(null);
export default function App() {
const [n, setN] = React.useState(0);
return (
// 2. 将包含读写API的数据传给Provider内的组件
<Context.Provider value={{ n, setN }}>
<Papa />
</Context.Provider>
);
}
完整代码:https://codesandbox.io/s/youthful-wildflower-wmssd
useEffect
用途:
改变外部环境,如修改document.title
模拟生命周期
参考:https://www.yuque.com/qingrenyoutiandi/grlnzp/gxnz4y
useLayoutEffect
useEffect在浏览器渲染完成之后执行
useLayoutEffect在浏览器渲染完成之前执行
useLayoutEffect总是比useEffect先执行
优先使用useEffect,有改变layout(修改DOM)才放useLayoutEffect
import React, { useState, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
const BlinkyRender = () => {
const [value, setValue] = useState(0);
useLayoutEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value]);
return (
<div id="x" onClick={() => setValue(0)}>value: {value}</div>
);
};
ReactDOM.render(
<BlinkyRender />,
document.querySelector("#root")
);
useMemo和useCallback
useMemo和useCallback主要用于性能优化
React会有多余的render, 在下面的例子中,如果修改N, 子组件Child中的m并没有改变,Child函数仍然会执行
import { useState } from "react";
function Child(props){
console.log("Child执行了")
return (
<>
<div>{props.m}</div>
</>
)
}
export default function App() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const addN = ()=>{setN(n+1)}
return (
<div>
<div>{n}</div>
<button onClick={addN}>changeN</button>
<div className="App">
<Child m={m}/>
</div>
</div>
);
}
使用React.memo()可以使组件只有在props变化的时候才重新执行
const Child2 = React.memo(Child)
可以把Child函数声明直接写到React.memo里
const Child = React.memo((props) => {
console.log("Child执行了");
return (
<>
<div>{props.m}</div>
</>
);
});
但是有一个bug,添加监听函数后一秒破功。因为只要App重新渲染,addM就会重新执行,子组件Child的props就变了,所以会重新渲染
import React, { useState } from "react";
const Child = React.memo((props) => {
console.log("Child执行了");
return (
<>
<div>{props.m}</div>
{// 添加监听函数}
<button onClick={props.addM}>changM</button>
</>
);
});
export default function App() {
const [n, setN] = useState(0);
const [m, setM] = useState(0);
const addN = () => {
setN(n + 1);
};
const addM = () => {
setM(m + 1);
};
return (
<div>
<div>{n}</div>
<button onClick={addN}>changeN</button>
<div className="App">
<Child m={m} addM={addM} />
</div>
</div>
);
}
使用useMemo
const addM = useMemo(() => {
const fn = () => setM(m + 1);
return fn;
}, [m]);
第一个参数()=>返回一个函数或值,第二个参数为依赖
只有当依赖变化时,才计算出新的value, 如果依赖不变,则重用之前的value
useMemo语法太繁琐了,每次都要写()=>fn/value,
为了不用写useMemo(() => () => { doSomething() })
于是就有了语法糖useCallback, 第一个参数只需要写回调函数() => { doSomething() }
const addM = useCallback(() => console.log("hello"), [m]);
完整代码:https://codesandbox.io/s/zealous-heisenberg-pzcn6
useRef
如果你需要一个值,在组件不断render时保持不变, 则使用useRef
初始化: const count = useRef(0)
读取: count.current
import { useEffect, useRef, useState } from "react";
export default function App() {
const [n, setN] = useState(0);
const count = useRef(0);
useEffect(() => {
count.current += 1;
console.log(count.current);
});
return (
<div className="App">
<div>{n}</div>
<button onClick={() => setN(n + 9)}>+9</button>
</div>
);
}
由于React的理念,当ref变化时,不会自动render
监听ref, 当ref.current变化时,调用setX即可
import { useEffect, useRef, useState } from "react";
import "./styles.css";
export default function App() {
const [_, set_] = useState(null);
const count = useRef(0);
return (
<div className="App">
<div>{count.current}</div>
<button
onClick={() => {
count.current += 1;
set_(Math.random());
console.log(count.current);
}}
>
count+1
</button>
</div>
);
}
forwardRef
props不包含ref, 不能传递ref属性,需要forwardRef实现ref的传递
import React, { useEffect, useRef } from "react";
import "./styles.css";
const Child = React.forwardRef((props, ref) => {
console.log(ref.current);
return <div ref={ref}>{props.msg}</div>;
});
export default function App() {
const ref = useRef(null);
useEffect(() => {
const div = ref.current; //DidMount后, ref.current指向当前DOM元素
console.log(div);
});
return (
<div className="App">
<Child msg={"hello"} ref={ref} />
</div>
);
}
useImperativeHandle
useImperativeHandle用于对 ref 的封装, 使其不引用DOM, 而是返回子组件内的数据或函数
import React, {
useRef,
useState,
useEffect,
useImperativeHandle,
createRef
} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.current);
});
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
<button
className="close"
onClick={() => {
console.log(buttonRef);
buttonRef.current.x();
}}
>
x
</button>
</div>
);
}
const Button2 = React.forwardRef((props, ref) => {
const realButton = useRef(null);
const setRef = useImperativeHandle;
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
自定义hook
import { useState, useEffect } from "react";
const useList = () => {
const [list, setList] = useState(null);
useEffect(() => {
ajax("/list").then(list => {
setList(list);
});
}, []); // [] 确保只在第一次运行
return {
list: list,
setList: setList
};
};
export default useList;
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: 1, name: "Frank" },
{ id: 2, name: "Jack" },
{ id: 3, name: "Alice" },
{ id: 4, name: "Bob" }
]);
}, 2000);
});
}
stale closure
过时的闭包
参考链接:https://dmitripavlutin.com/react-hooks-stale-closures/