React Hooks
官网:https://zh-hans.reactjs.org/docs/hooks-state.html#gatsby-focus-wrapper
Hook 是什么?
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState
是允许你在 React 函数组件中添加 state 的 Hook。稍后我们将学习其他 Hook。
什么时候我会用 Hook?
如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hook。
使用Hook的限制:
- 只能写在函数组件和自定义Hook
- 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们
一、useState
1.声明
以往在class中声明一个变量count
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
使用useState声明一个count
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量
const [count, setCount] = useState(0);
const [count, setCount] = useState(0);
count:自定义state变量名,这里等同于this.state.count
setCount:自定义函数名,等同于this.setSate
useState只有一个参数,即为count的初始值,这里意为count初始值为0
2.读取
class
<p>You clicked {this.state.count} times</p>
函数
<p>You clicked {count} times</p>
3.更新
class
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
函数
<button onClick={() => setCount(count + 1)}>
Click me
</button>
二、useEffect
Effect Hook 可以让你在函数组件中执行副作用操作
如果你熟悉 React class 的生命周期函数,你可以把 useEffect
Hook 看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
1.使用
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 异步,第一次渲染和每次更新完毕后执行 Effect 内操作
useEffect(() => {
console.log(`You clicked ${count} times`)
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2.清除
React 何时清除 effect? React 会在组件卸载的时候执行清除操作。
effect 在每次渲染的时候都会执行,所以 React 会在执行当前 effect 之前对上一个 effect 进行清除。
effect 如果返回一个函数,React 将会在执行清除操作时调用它
useEffect(() => {
console.log(`You clicked ${count} times`)
return function(){
console.log('清除 effect')
}
});
3.跳过 Effect 进行性能优化
useEffect(() => {
console.log(`You clicked ${count} times`)
return function(){
console.log('清除 effect')
}
}, [count]); // 仅在 count 更改时更新
如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)
如果你传入了一个空数组([]
),effect 内部的 props 和 state 就会一直拥有其初始值
三、useContext
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性
import React, { createContext, useContext } from 'react';
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 通过createContext在组件外创建 Context 对象,createContext(defaultValue)
// 使用 Context 对象的组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值
// 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
const ThemeContext = createContext(themes.light);
function App() {
return (
// ThemeContext.Provider提供了一个Context对象,这个对象是可以被子组件共享的。
<ThemeContext.Provider value={themes.dark}>
{/* Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据 */}
{/* 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染 */}
<Toolbar />
</ThemeContext.Provider>
);
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
// useContext 的参数必须是 context 对象本身
const theme = useContext(ThemeContext);
// 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
四、useReducer(可以先熟悉 Redux )
// 指定初始state
const [state, dispatch] = useReducer(reducer, {count: initialCount});
const [参数名,dispatch]= useReducer(reducer, 初始值);
// 惰性初始化
const [state, dispatch] = useReducer(reducer, initialArg, init);
const [参数名,dispatch]= useReducer(reducer, 初始函数传入值, init初始函数);
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
// 计数器示例
import React, { useReducer } from 'react';
// init函数
function init(initialCount) {
return { count: initialCount };
}
// reducer
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter() {
// useReducer(reducer, 传入init的初始值, init)
const [state, dispatch] = useReducer(reducer, 0, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({ type: 'reset', payload: 0 })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
五、useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
// 示例:输入框聚焦
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useRef()
和自建一个 {current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象
当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
六、自定义Hook
自定义 Hook 是一个函数,其名称以 “use
” 开头,函数内部可以调用其他的 Hook
共享逻辑,代码封装
自定义useReducer示例:
import React, { useState } from 'react';
// 自定义 useReducer ,命名用 "use"
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
// 返回state 和 dispatch
return [state, dispatch];
}
// reducer 规则
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
throw new Error();
}
}
function App() {
// 使用自定义 Hook
const [state, dispatch] = useReducer(reducer, 0);
function handleIncrement() {
dispatch({ type: 'increment' });
}
function handleDecrement() {
dispatch({ type: 'decrement' });
}
return (
<div>
{state}
<button onClick={() => handleIncrement()}>按钮+</button>
<button onClick={() => handleDecrement()}>按钮-</button>
</div>
)
}