Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
1.useState
用来声明state变量,类似于class里的this.state,使用方法如下
export default function Example() {
// useState() 方法里面唯一的参数就是初始 state,可以是数字或字符串或对象
// 这一步设置了count的初始值为0
// setCount为修改count这个状态的方法
// 调用一次setCount,React会重新渲染 Example 组件
const [count, setCount] = useState(0)
return (
<div>
you count: {count}
{/* 每次点击按钮调用setCount,会让count + 1 */}
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
)
}
自定义useState
某些时候我们需要自定义 Hook,这可以将组件逻辑提取到可重用的函数中
// 自定义的Hook需要以大写字母开头
// 这里的Hook只控制count + 1,因此可以复用
// 我们只需多次使用,就如下面的count1和count2,他们是互相独立的
function UseState(initvalValue) {
const [count, setCount] = useState(initvalValue)
return [
count,
() => {
setCount(count + 1)
}
]
}
export default function Example() {
const [count1, addCount1] = UseState(0)
const [count2, addCount2] = UseState(0)
return (
<div>
you count1: {count1}
{/* 每次点击按钮调用setCount,会让count + 1 */}
<button onClick={addCount1}>Add1</button>
you count2: {count2}
{/* 每次点击按钮调用setCount,会让count + 1 */}
<button onClick={addCount2}>Add2</button>
</div>
)
}
2.useEffect
Effect Hook 可以让你在函数组件中执行副作用操作,类似于componentDidMount 和componentDidUpdate
export default () => {
const [count, setCount] = useState(0);
// 在渲染后将执行该操作,并且在执行DOM更新之后也会调用它
// effect可以返回一个清除函数,如此可以清除上一步的操作
// 可以接受两个参数,第一个就是操作本身,第二个是更新的依赖
// 可以选择不传,不传将依赖所有state变化
// 如果传入空,或者传一个确定的数如[1],那么该函数只会执行一次,即第一次渲染
// 可以传[count],则表示资源count变化才会调用
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// 当然可以定义多个effect函数,它们会各自执行
const [person, setPerson] = useState(1);
useEffect(() => {
//
}, [person]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
自定义useEffect
某些时候我们需要自定义 Hook,这可以将组件逻辑提取到可重用的函数中
// 这里实现一个count自动 +1 的函数
// 自定义的Hook需要以大写字母开头
// 可以不传useEffect的第二个参数,这和传入callback, time一样
// 如果第二个传入为空将会出现神奇的事情:
// 会发现console会一直打印0,但是count却一直都是1
// 这是因为未传依赖导致每次更新都在运行第一个延时函数,如果需要变化count可以有两种方法
// 第一种是,传第二个参数为[callback, time]或者不传第二个参数
// 第二种是,将Example里的setCount(count + 1)改为setCount(count => count + 1)
// 这种方法将会改变作用域,每次都会取最新的count,大多数情况下都推荐以函数的形式
function UserInterval(callback, time) {
useEffect(() => {
const i = setInterval(callback, time)
return () => {
clearInterval(i)
}
}, [callback, time])
}
export default function Example() {
const [count, setCount] = useState(0)
// 第一种:传第二个参数为[callback, time]或者不传第二个参数
UserInterval(() => {
console.log(count)
setCount(count => count + 1)
}, 1000)
// 第二种:将Example里的setCount(count + 1)改为setCount(count => count + 1)
// UserInterval(() => {
// console.log(count)
// setCount(count => count + 1)
// }, 1000)
return (
<div>
you count: {count}
</div>
)
}
3.useContext
接收一个context对象(React.createContext 的返回值)并返回该 context 的当前值
适用于多个函数需要使用一个值,但是不推荐直接将该值引进函数中,这会造成函数本身冗杂
因此需要通过父传子的形式一层层传递,但是这是非常麻烦的,我们可以使用useContext来直接访问
// 以白黑切换来讲解
// 这里创建一个白黑模式样式
const themes = {
light: {
background: '#fff'
},
dark: {
background: '#000'
}
}
// 创建一个context对象,包含白黑切换方法以及当前颜色
const ThemeContext = React.createContext({
theme: themes.light,
toggle: () => { }
})
export default function Example() {
const [theme, setTheme] = useState(themes.light)
// 需要读取 context 的值以及订阅 context 的变化
// 仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。
// MyContext为自定义的,本文为ThemeContext
return <ThemeContext.Provider value={{
theme,
toggle: () => { setTheme(theme === themes.light ? themes.dark : themes.light) }
}}>
<Toolbar />
</ThemeContext.Provider>
}
const Toolbar = () => {
return <ThemeBitton />
}
// 如果需要拿到Example组件里的theme,我们需要传递三层,十分麻烦
// 因此可以使用useContext来连接ThemeContext,直接操作theme
const ThemeBitton = () => {
// useContext接收一个 context 对象(React.createContext 的返回值)
// 并返回该 context 的当前值
const context = useContext(ThemeContext)
return <button style={{
background: context.theme.background
}} onClick={context.toggle}>Click Me</button>
}
4.useReducer
它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
如果你熟悉 Redux 的话,就已经知道它如何工作了
// 创建一个reducer, 存储satate
function reducer(state, action) {
switch (action.type) {
case 'add':
return state + 1
case 'sub':
return state - 1
default:
return state
}
}
export default () => {
// 有两种不同初始化 useReducer state 的方式,你可以根据使用场景选择其中的一种
// 第一个参数即为自定义的reducer
// 第一种,将初始 state 作为第二个参数传入 useReducer 是最简单的方法
// counter即被设置为reducer返回的state
// 注意:state可以为对象,那么传入初始值也需为对象
const [counter, dispatch] = useReducer(reducer, 0)
return (
<div>
count:{counter}
{/* 通过dispatch来触发reducer里的action来改变state的值
参数即为action */}
<button onClick={() => dispatch({ type: 'add' })}>+</button>
<button onClick={() => dispatch({ type: 'sub' })}>-</button>
</div>
)
}
// function init(initialCount) {
// // do something
// return initialCount;
// }
// export default function Example(initialCount) {
// //第二种,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)
// //这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利
// const [counter, dispatch] = useReducer(reducer, initialCount, init);
// return (
// <div>
// count:{counter}
// {/* 通过dispatch来触发reducer里的action来改变state的值
// 参数即为action */}
// <button onClick={() => dispatch({ type: 'add' })}>+</button>
// <button onClick={() => dispatch({ type: 'sub' })}>-</button>
// </div>
// )
// }
5.useContext + useReducer 实现 类似redux
import { useReducer, createContext, useContext, Component } from 'react';
const initState = {
count: 0,
list: [],
};
const reducer = (state, action) => {
const newState = JSON.parse(JSON.stringify(state));
switch (action.type) {
case 'Add':
if (action.data) {
newState.count += action.data;
} else {
newState.count++;
}
return newState;
case 'Append':
newState.list.push(action.data);
return newState;
default:
return newState;
}
};
// 创建 context
const Context = createContext();
const ContextProvider = ({ children }) => {
// 创建 reducer
const [state, dispatch] = useReducer(reducer, initState);
return (
<Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
);
};
const App = () => {
const { state, dispatch } = useContext(Context);
const { count, list } = state;
const add = (v) => {
dispatch({ type: 'Add', data: v });
};
return (
<div>
<span>{count}</span>
<button onClick={() => add()}>Add</button>
<button onClick={() => add(20)}>Add 20</button>
<ul>
{list.map((v) => (
<li key={v}>{v}</li>
))}
<p onClick={() => dispatch({ type: 'Append', data: Date.now() })}>
添加
</p>
</ul>
<ClassComponent />
</div>
);
};
class ClassComponent extends Component {
state = {};
componentWillMount() {
console.log('ClassComponent componentWillMount');
}
componentDidMount() {
console.log('ClassComponent componentDidMount');
}
add = () => {
this.context.dispatch({ type: 'Add' });
};
render() {
const { state, dispatch } = this.context;
const { count, list } = state;
return (
<div>
<span>{count}</span>
<button onClick={() => this.add()}>Add</button>
<ul>
{list.map((v) => (
<li key={v}>{v}</li>
))}
<p onClick={() => dispatch({ type: 'Append', data: Date.now() })}>
添加
</p>
</ul>
</div>
);
}
}
ClassComponent.contextType = Context;
export default function () {
return (
<ContextProvider>
<App />
</ContextProvider>
);
}
6.useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
返回的 ref 对象在组件的整个生命周期内保持不变
// 点击按钮focus输入框
export default () => {
const refInput = useRef()
return (
<div>
<input ref={refInput} />
<button onClick={() => {
// 需要使用.current拿到dom元素
refInput.current.focus()
}}>Focus</button>
</div>
)
}
7.一些额外的Hook
还有一些额外的Hook,这里不详细介绍
useCallback 把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
useMemo 把“创建”函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码
useLayoutEffect 其函数签名与useEffect
相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染
useDebugValue useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签
这些比较少用,各位可以前往React官网的Hook栏学习