React Hooks
什么是hooks?
Hook 的含义
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。
所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
useState(状态)
使用状态
const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:"xiaohong"})
注意事项1;(不可局部更新)
import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
const [user,setUser] = useState({name:'Frank', age: 18})
const onClick = ()=>{
setUser({
name: 'Jack'
})
}
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不会帮我们合并属性
注意事项2 :地址要变
useState(obj),如果obj的地址不变,React就会认为没有变化
useState函数
const [state,setstate] =useState(()=>{return initialState
})
//该函数返回初始的state,并且执行一次
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) // 你会发现 n 不能加 2
// 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);
useReducer( 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer )
useReducer 和 redux 的区别
- useReducer 是 useState的代替方案,用于 state 复杂变化
- useReducer 是单个组件状态管理,组件通讯还需要 props
- redux 是全局状态管理,多组件共享数据
初步使用(实现加一功能)
- 创建初始化initialState的值
const initialState ={n:0}
- 创建所有操作reducer(state,action)
const reducer = (state,action) => {
if(action.type === 'add') {
return {n:state.n+1}
} else if(action.type==='mult') {
return {n:state.n*2}
} else {
throw new Error('error')
}
}
- 传给useReducer,得到读和写的api
const App = (PROPS)=>{
//得到读和写的api
const [state,dispatch] = React.useReducer(reducer,initialState)
const {n} = state
return(
<div>
<div>
<p>{n}</p>
<button onClick=
//使用写的操作实现加一功能
{()=>dispatch({type:'add'})}>+1</button>
</div>
</div>
)
}
export default App
useReducer和useState的复杂版
import React, { useState, useReducer } from "react";
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>
);
}
export default App
useReducer 的表单例子
import React, { useState, useReducer } from "react";
const initFormData = {
name: "",
age: 18,
nationality: "汉族"
};
function reducer(state, action) {
switch (action.type) {
case "patch":
return { ...state, ...action.formData };
case "reset":
return initFormData;
default:
throw new Error();
}
}
function App() {
const [formData, dispatch] = useReducer(reducer, initFormData);
// const patch = (key, value)=>{
// dispatch({ type: "patch", formData: { [key]: value } })
// }
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: "patch", formData: { age: e.target.value } })
}
/>
</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>
);
}
export default App
如何替代Redux(步骤)
- 将数据集中在一个store对象
- 将所有操作集中在reducer
- 创建一个Context
- 创建对数据的读写api
- 将第四步内容放到第三步的Context
- 用Context.Provider将Context提供给所有组件
- 各个组件用useContext获取读写api
示例代码
import React, { useReducer, useContext, useEffect } from "react";
const store = {
user: null,
books: null,
movies: null
};
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();
}
}
const Context = React.createContext(null);
function App() {
const [state, dispatch] = useReducer(reducer, store);
const api = { state, dispatch };
return (
<Context.Provider value={api}>
<User />
<hr />
<Books />
<Movies />
</Context.Provider>
);
}
function User() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/user").then(user => {
dispatch({ type: "setUser", user: user });
});
}, []);
return (
<div>
<h1>个人信息</h1>
<div>name: {state.user ? state.user.name : ""}</div>
</div>
);
}
function Books() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/books").then(books => {
dispatch({ type: "setBooks", books: books });
});
}, []);
return (
<div>
<h1>我的书籍</h1>
<ol>
{state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
</ol>
</div>
);
}
function Movies() {
const { state, dispatch } = useContext(Context);
useEffect(() => {
ajax("/movies").then(movies => {
dispatch({ type: "setMovies", movies: movies });
});
}, []);
return (
<div>
<h1>我的电影</h1>
<ol>
{state.movies
? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>)
: "加载中"}
</ol>
</div>
);
}
// 帮助函数
// 假 ajax
// 两秒钟后,根据 path 返回一个对象,必定成功不会失败
function ajax(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path === "/user") {
resolve({
id: 1,
name: "Frank"
});
} else if (path === "/books") {
resolve([
{
id: 1,
name: "JavaScript 高级程序设计"
},
{
id: 2,
name: "JavaScript 精粹"
}
]);
} else if (path === "/movies") {
resolve([
{
id: 1,
name: "爱在黎明破晓前"
},
{
id: 2,
name: "恋恋笔记本"
}
]);
}
}, 2000);
});
}
export default App
如何做代码优化? 查看代码
useContext(上下文)
- 全局变量是全局的上下文
- 上下文是局部的全局变量
使用方法
- 使用C=createConText(initial)创建上下文
- 使用<C.provider>圈定作用域
- 在作用域内使用useContext(C)来使用上下文
示例代码
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const C = createContext(null);
function App() {
console.log("App 执行了");
const [n, setN] = useState(0);
return (
<C.Provider value={{ n, setN }}>
<div className="App">
<Baba />
</div>
</C.Provider>
);
}
function Baba() {
const { n, setN } = useContext(C);
return (
<div>
我是爸爸 n: {n} <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里面的值改变,零一个模块不会感知到这个变化
useEffect(副作用)
对环境的改变即为副作用,如修改document.title
用途模拟生命周期
- 模拟(componentDidMount)使用,[]作第二个才是
useEffect(()=>{
console.log('第一次渲染')
},[])
- 模拟(componentDidUpdate)使用,可指定依赖
useEffect(()=>{
console.log('n变啦')
},[n])
- 模拟(componentWillUnmount)使用 ,通过return 函数
useEffect(()=>{
return ()=>{
console.log('我要消失啦')
}
})
特点: 如果同时存在多个useEffect,会按照出现次序执行
useLayoutEffect(布局副作用)
useLayoutEffect在浏览器渲染完成后执行
import React, { useLayoutEffect,useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const BlinkyRender = () => {
const [value, setValue] = useState(0);
useEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value]);
useLayoutEffect(()=>{
console.log('useLayoutEffect')
},[value])
return (
<div id="x" onClick={() => setValue(0)}>value: {value}</div>
);
};
ReactDOM.render(
<BlinkyRender />,
document.querySelector("#root")
);
useLayoutEffect 在浏览器渲染前执行
特点
- useLayoutEffect 总是比useEffect先执行
- useLayoutEffect 里的任务最终影响了Layout
为了用户体验,优先使用useEffect
useMemo
React.memo(在组件变化是才渲染)
示例代码
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
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>
);
}
const 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);
代码中的Child用React.memo(chlid代替
如果Props不变,就没有必要再次执行函数组件
作用效果代码代码
代码(bug)
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClickChild = () => {
console.log(m);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
{/* Child2 居然又执行了 */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={props.onClick}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
上面的代码运行点击按钮之后,Child2执行了?
因为App运行时会在执行第12行,生成新的函数。新旧函数虽然功能一样,但是地址不一样!所有Child2执行了
用useMemo可以解决
import React, { useMemo } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m + 1);
};
const onClickChild = useMemo(() => {
const fn = div => {
console.log("on click child, m: " + m);
console.log(div);
};
return fn;
}, [m]); // 这里呃 [m] 改成 [n] 就会打印出旧的 m
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useMemo (的特点)
- 第一个参数树()=>value
- 第二个参数是依赖[m,n],只能当依赖变化时,才会计算出新的value。如果依赖不变,那么就重用之前的value
注意
- 如果你的value是个函数,那么你就要写成
useMemo(()=>(X)=>console.log(x))
这是一个返回函数的函数。很难用,于是就有了useCsllbsck
useCallback(语法糖)
用法
useCallback(X=> console.log(x),[m])
等价于
useMemo(()=>(X)=>console.log(x))
useRef(不变的值)
目的
如果你需要一个值,在组件不断render是保持不变,初始化值
const count = useRef(0)
读取值
count.current
为什么需要current?
为了保证两次useRef是同一个值(只有引用能做到)
看看vue3 的ref
初始化:const count = ref(0)
读取:count.value
不同点:单count.value变化时,vue3 会自动render
useRef不能做到自动render
forwardRef
props无法传递ref属性
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
{/* 看浏览器控制台的报错 */}
</div>
);
}
const Button2 = props => {
return <button className="red" {...props} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
实现ref的传递
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
两次ref传递得到button的医用
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const MovableButton = movable(Button2);
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.curent);
});
return (
<div className="App">
<MovableButton name="email" ref={buttonRef}>
按钮
</MovableButton>
</div>
);
}
// function Button2(props) {
// return <button {...props} />;
// }
const Button2 = React.forwardRef((props, ref) => {
return <button ref={ref} {...props} />;
});
// 仅用于实验目的,不要在公司代码中使用
function movable(Component) {
function Component2(props, ref) {
console.log(props, ref);
const [position, setPosition] = useState([0, 0]);
const lastPosition = useRef(null);
const onMouseDown = e => {
lastPosition.current = [e.clientX, e.clientY];
};
const onMouseMove = e => {
if (lastPosition.current) {
const x = e.clientX - lastPosition.current[0];
const y = e.clientY - lastPosition.current[1];
setPosition([position[0] + x, position[1] + y]);
lastPosition.current = [e.clientX, e.clientY];
}
};
const onMouseUp = () => {
lastPosition.current = null;
};
return (
<div
className="movable"
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
style={{ left: position && position[0], top: position && position[1] }}
>
<Component {...props} ref={ref} />
</div>
);
}
return React.forwardRef(Component2);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useRef
可以用来引用DOM对象
也可以用来引用普通对象
forwardRef
由于propos不包含ref,所以需要forwardRef
为什么props不包含ref呢?因为大部分时候不需要
useImperativeHandle(用于自定义ref属性)
应该叫setRef
不用 useImperativeHandle的代码
import React, { useRef, useEffect } 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.remove();
}}
>
x
</button>
</div>
);
}
const Button2 = React.forwardRef((props, ref) => {
return <button ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
用了useImperativeHandle的代码
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 = createRef(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);
自定义Hooks
- 示例1
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
const { list, setList } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
- 示例2
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
const { list, deleteIndex } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map((item, index) => (
<li key={item.id}>
{item.name}
<button
onClick={() => {
deleteIndex(index);
}}
>
x
</button>
</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);