Ссылка на живое воспроизведение:Сообщество Юнци (@x-cold)
Что такое React-хуки?
Хуки, как следует из названия, буквально представляют собой концепцию хуков React. С кейсом мы получаем первое впечатление от React Hooks.
Предположим, теперь вы хотите реализовать компонент счетчика. Если мы используем компонентизацию, нам нужно делать больше вещей, таких как объявление состояния, написание методов счетчика и т. д., и нам может понадобиться понимать больше концепций, таких как концепция классов Javascript, указание этого контекста и т. д.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
state = {
count: 0
}
countUp = () => {
const { count } = this.state;
this.setState({ count: count + 1 });
}
countDown = () => {
const { count } = this.state;
this.setState({ count: count - 1 });
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp}>+</button>
<h1>{count}</h1>
<button onClick={this.countDown}>-</button>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('root'));
С помощью React Hooks мы можем написать это.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<h1>{count}</h1>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
Из приведенных выше примеров видно, что React Hooks обеспечивают чистый, функциональный (FP) стиль программирования, который обеспечивает взаимодействие состояния с пользовательским интерфейсом (MVVM) через чистые функциональные компоненты и контролируемый поток данных.
Hooks API
useState
useState — самый простой API, он передает начальное значение и получает новое значение при каждом выполнении функции.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<h1>{count}</h1>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
Следует отметить, что счетчик состояний, полученный через useState, представлен в виде константы в компоненте Counter, после каждой модификации через setCount новая константа снова получается через useState.
useReducer
useReducer и useState почти одинаковы и требуют внешнего внешнего редьюсера (глобального), который может одновременно управлять несколькими состояниями. Если присмотреться, это на самом деле очень близко к концепции потока данных в редуксе.
import { useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function reducer(state, action) {
switch (action.type) {
case 'up':
return { count: state.count + 1 };
case 'down':
return { count: state.count - 1 };
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 1 })
return (
<div>
{state.count}
<button onClick={() => dispatch({ type: 'up' })}>+</button>
<button onClick={() => dispatch({ type: 'down' })}>+</button>
</div>
);
}
ReactDOM.render(<Counter />, document.getElementById('root'));
useEffect
Ключевой API-интерфейс Hooks, как следует из названия, useEffect используется для обработки побочных эффектов, вызванных различными изменениями состояния, то есть логики, которая будет выполняться только в определенный момент.
import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
function Example() {
const [count, setCount] = useState(0);
// => componentDidMount/componentDidUpdate
useEffect(() => {
// update
document.title = `You clicked ${count} times`;
// => componentWillUnMount
return function cleanup() {
document.title = 'app';
}
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
useMemo
useMemo в основном используется для оптимизации процесса рендеринга.Двумя параметрами являются функция расчета (обычно функция компонента) и список зависимых состояний.При изменении зависимого состояния будет запущено выполнение функции расчета. Если зависимости не указаны, функция расчета будет выполняться на каждом проходе рендеринга.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import { useState, useMemo } from 'react';
import ReactDOM from 'react-dom';
function Time() {
return <p>{Date.now()}</p>;
}
function Counter() {
const [count, setCount] = useState(0);
const memoizedChildComponent = useMemo((count) => {
return <Time />;
}, [count]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<div>{memoizedChildComponent}</div>
</div>
);
}
ReactDOM.render(<Counter />, document.getElementById('root'));
useContext
Контекст - это состояние внешнего создания и внутреннего использования. Разница между ним и глобальными переменными заключается в том, что если несколько компонентов используют контекст одновременно, то эти компоненты будут отображаться. Если несколько компонентов используют состояние в одной и той же глобальной переменной одновременно, только текущий компонент, который запускает повторную визуализацию setState.
Пример — useContext не используется
import { useState, useContext, createContext } from 'react';
import ReactDOM from 'react-dom';
// 1. 使用 createContext 创建上下文
const UserContext = new createContext();
// 2. 创建 Provider
const UserProvider = props => {
let [username, handleChangeUsername] = useState('');
return (
<UserContext.Provider value={{ username, handleChangeUsername }}>
{props.children}
</UserContext.Provider>
);
};
// 3. 创建 Consumer
const UserConsumer = UserContext.Consumer;
// 4. 使用 Consumer 包裹组件
const Pannel = () => (
<UserConsumer>
{({ username, handleChangeUsername }) => (
<div>
<div>user: {username}</div>
<input onChange={e => handleChangeUsername(e.target.value)} />
</div>
)}
</UserConsumer>
);
const Form = () => <Pannel />;
const App = () => (
<div>
<UserProvider>
<Form />
</UserProvider>
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
Пример — использование useContext
import { useState, useContext, createContext } from 'react';
import ReactDOM from 'react-dom';
// 1. 使用 createContext 创建上下文
const UserContext = new createContext();
// 2. 创建 Provider
const UserProvider = props => {
let [username, handleChangeUsername] = useState('');
return (
<UserContext.Provider value={{ username, handleChangeUsername }}>
{props.children}
</UserContext.Provider>
);
};
const Pannel = () => {
const { username, handleChangeUsername } = useContext(UserContext); // 3. 使用 Context
return (
<div>
<div>user: {username}</div>
<input onChange={e => handleChangeUsername(e.target.value)} />
</div>
);
};
const Form = () => <Pannel />;
const App = () => (
<div>
<UserProvider>
<Form />
</UserProvider>
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
useRef
useRef возвращает изменяемый объект ref, свойство .current которого инициализируется переданным параметром (initialValue). Возвращенный объект будет сохраняться в течение всего времени существования компонента. На самом деле useRef — очень полезный API, во многих случаях нам нужно сохранить что-то измененное, пригодится.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Схема обмена состоянием React
Когда дело доходит до совместного использования состояния, самый простой и прямой способ — это пошаговая передача состояния через свойства.Этот метод связан с отношениями родителей и детей компонентов.Как только структура вложенности компонентов изменится, код необходимо переписать. , а стоимость обслуживания очень дорогая. Со временем были представлены различные решения для решения проблем совместного использования состояния и повторного использования кода.
Mixins
В React миксины могут использовать только компоненты, созданные с помощью createClass. Этот тесно связанный, трудноуправляемый и очень сложный подход постепенно исчез с исторической сцены с волной ES6.
HOC
Компоненты более высокого порядка происходят из функционального программирования.Поскольку компоненты в React также можно рассматривать как функции (классы), повторное использование кода может быть достигнуто с помощью HOC. Это может быть реализовано через прокси свойств и обратное наследование, HOC может легко манипулировать результатами рендеринга, а также может работать с реквизитами/состоянием компонентов, так что сложную логику кода можно легко использовать повторно.
import React from 'react';
import PropTypes from 'prop-types';
// 属性代理
class Show extends React.Component {
static propTypes = {
children: PropTypes.element,
visible: PropTypes.bool,
};
render() {
const { visible, children } = this.props;
return visible ? children : null;
}
}
// 反向继承
function Show2(WrappedComponent) {
return class extends WrappedComponent {
render() {
if (this.props.visible === false) {
return null;
} else {
return super.render();
}
}
}
}
function App() {
return (
<Show visible={Math.random() > 0.5}>hello</Show>
);
}
Повторное использование состояния в Redux — это типичная реализация HOC.Мы можем использовать compose для сборки данных в целевые компоненты.Конечно, вы также можете использовать декораторы.
import React from 'react';
import { connect } from 'react-redux';
// use decorator
@connect(state => ({ name: state.user.name }))
class App extends React.Component{
render() {
return <div>hello, {this.props.name}</div>
}
}
// use compose
connect((state) => ({ name: state.user.name }))(App);
Render Props
Очевидно, что renderProps — это решение для передачи метода рендеринга в качестве реквизита дочерним компонентам.По сравнению с решением HOC, renderProps может защитить исходную иерархию компонентов.
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 与 HOC 不同,我们可以使用具有 render prop 的普通组件来共享代码
class Mouse extends React.Component {
static propTypes = {
render: PropTypes.func.isRequired
}
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
function App() {
return (
<div style={{ height: '100%' }}>
<Mouse render={({ x, y }) => (
// render prop 给了我们所需要的 state 来渲染我们想要的
<h1>The mouse position is ({x}, {y})</h1>
)}/>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
Hooks
Комбинируя Hooks API и встроенный контекст React, вы можете видеть из предыдущего примера, что Hooks делают разделение состояния между компонентами более четким и простым.
Философия дизайна React Hooks
Фундаментальный
function FunctionalComponent () {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(2);
const [state3, setState3] = useState(3);
}
{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
}
Функциональная реализация до конца
capture props
Функциональные компоненты естественно поддерживают свойства, и их основное использование не сильно отличается от компонентов класса. Следует отметить два отличия:
- Реквизиты компонента класса монтируются в этом контексте, а функциональные компоненты передаются через формальные параметры;
- Из-за разницы в позиции монтирования, если this изменится в компоненте класса, изменится и this.props, а в функциональном компоненте реквизиты всегда неизменяемы, поэтому следуйте принципу захвата значения (то есть полученное значение всегда В какой-то момент), хуки также следуют этому принципу.
черезПримерЧтобы понять значение захвата, мы можем обойти значение захвата с помощью useRef, потому что useRef является изменяемым.
state
компонент класса | функциональный компонент | |
---|---|---|
Создать состояние | this.state = {} | useState, useReducer |
Изменить статус | this.setState() | set function |
механизм обновления | Асинхронное обновление, несколько модификаций объединяются в предыдущее состояние, в результате чего создается копия. | Синхронное обновление, непосредственно измененное до целевого состояния |
государственное управление | Одно государство централизованно управляет несколькими государствами | Несколько состояний, которые можно объединить с помощью useReducer (вручную) |
представление | высоко | Если состояние инициализации useState необходимо получить с помощью очень сложных вычислений, используйте метод объявления функции, иначе каждый рендеринг будет повторяться. |
Жизненный цикл
- componentDidMount / componentDidUpdate / componentWillUnMount
useEffect будет вызываться при каждом рендеринге, и его можно использовать в качестве этих жизненных циклов с небольшой оберткой;
- shouldComponentUpdate
Обычно мы оптимизируем производительность компонентов исключительно в порядке приоритета, чтобы уменьшить количество компонентов, отображающих отдельные компоненты.
class Button extends React.PureComponent {}
В React Hooks вместо этого можно использовать useMemo для повторного рендеринга компонентов только при изменении некоторых данных, что эквивалентно shouldComponentUpdate с smallEqual.
форсировать рендеринг forceUpdate
Поскольку по умолчанию каждый раз, когда состояние изменяется, это вызывает повторный рендеринг, который можно использовать как forceUpdate через неиспользуемую функцию set.
const forceUpdate = () => useState(0)[1];
Принцип реализации
На основе хуков, улучшенные хуки
Давайте получим набор ударов!
Поскольку каждый Hooks API представляет собой чисто функциональную концепцию, при его использовании больше внимания уделяется вводу (input) и выводу (output), поэтому лучше комбинировать базовый Hooks API с разными характеристиками, собирая функции для создания новых Hooks. API с различными функциями.
- useState поддерживает состояние компонента
- useEffect обрабатывает побочные эффекты
- useContext прослушивает изменения обновления поставщика
useDidMount
import { useEffect } from 'react';
const useDidMount = fn => useEffect(() => fn && fn(), []);
export default useDidMount;
useDidUpdate
import { useEffect, useRef } from 'react';
const useDidUpdate = (fn, conditions) => {
const didMoutRef = useRef(false);
useEffect(() => {
if (!didMoutRef.current) {
didMoutRef.current = true;
return;
}
// Cleanup effects when fn returns a function
return fn && fn();
}, conditions);
};
export default useDidUpdate
useWillUnmount
Я уже упоминал useEffect, который позволяет вернуть функцию очистки, которая будет выполняться при размонтировании компонента, так что useWillUnmount тоже легко реализовать~
import { useEffect } from 'react';
const useWillUnmount = fn => useEffect(() => () => fn && fn(), []);
export default useWillUnmount;
useHover
// lib/onHover.js
import { useState } from 'react';
const useHover = () => {
const [hovered, set] = useState(false);
return {
hovered,
bind: {
onMouseEnter: () => set(true),
onMouseLeave: () => set(false),
},
};
};
export default useHover;
import { useHover } from './lib/onHover.js';
function Hover() {
const { hovered, bind } = useHover();
return (
<div>
<div {...bind}>
hovered:
{String(hovered)}
</div>
</div>
);
}
useField
// lib/useField.js
import { useState } from 'react';
const useField = (initial) => {
const [value, set] = useState(initial);
return {
value,
set,
reset: () => set(initial),
bind: {
value,
onChange: e => set(e.target.value),
},
};
}
export default useField;
import { useField } from 'lib/useField';
function Input {
const { value, bind } = useField('Type Here...');
return (
<div>
input text:
{value}
<input type="text" {...bind} />
</div>
);
}
function Select() {
const { value, bind } = useField('apple')
return (
<div>
selected:
{value}
<select {...bind}>
<option value="apple">apple</option>
<option value="orange">orange</option>
</select>
</div>
);
}
Меры предосторожности
- Сфера использования Hook: в функциональных компонентах React, в пользовательских функциях Hook;
- Хук должен быть написан в самом внешнем слое функции.Каждый раз, когда useState меняет свой индекс (курсор), React обновляет состояние в соответствии со своим порядком;
- Хотя Hook API выполняется при каждом рендеринге, результирующее состояние всегда является константой (в пределах функции);
Эпилог
React Hooks предоставляют новые возможности для управления состоянием.Хотя нам может потребоваться поддерживать некоторое внутреннее состояние, мы можем избежать сложных способов управления состоянием, таких как renderProps/HOC. Преимущества хуков следующие:
- Более детальное повторное использование кода без чрезмерных побочных эффектов
- Функциональный стиль программирования, код более лаконичен, при этом снижается порог использования и понимания
- Уменьшить количество уровней вложенности компонентов
- Поток данных компонентов более четкий
На самом деле, настраивая пользовательские хуки для различных сценариев, мы можем сделать наше приложение более удобным и лаконичным, иерархию компонентов можно сохранить нетронутой, а такой приятный стиль функционального программирования, хуки в React 16.8 версии .0 официально выпустили стабильную версию, начните использовать ее прямо сейчас! ! !