Демо -приложение
Мы будем создавать простое приложение для рецептов и показывать, чтобы удерживать состояние в приложении React
Код приложения
Мы будем держать данные в jsonblob здесь
https://jsonblob.com/api/jsonBlob/fddd0cec-8e0e-11ea-82f0-13fba022ad5b
Файл index.js является просто основным файлом для запуска нашего приложения.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
,
document.getElementById('root')
);
Внутри файла app.js у нас будет apiurl Чтобы удерживать источник данных, компонент для именованных рецептов Усильщик , компонент для каждого рецепта с именем Рецепт и основное приложение компонента, которое будет обертка для рецептов. У нас будет пустой заголовок и нижний колонтитул. Мы добавим Bootstrap для укладки приложения.
const apiURL = `https://jsonblob.com/api/jsonBlob/fddd0cec-8e0e-11ea-82f0-13fba022ad5b`;
const RecipeList = ({ recipes }) => (
Recipe List
{recipes.map((recipe) => )}
);
const Recipe = ({ recipe }) => {
const { readyInMinutes, title, id, sourceUrl } = recipe;
return (
{title}
ready In Minutes: {readyInMinutes}
)
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
apiResponse: [],
loading: true,
};
}
componentDidMount() {
fetch(apiURL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
return response.json();
})
.then((apiResponse) => {
this.setState({ apiResponse });
this.setState({ loading: false });
})
.catch((e) => console.error(e));
}
render() {
let recipes = this.state.apiResponse.results;
let loading = this.state.loading;
return (
<>
header
{loading ? (
loading recipes ...
) : (
)}
footer
);
}
}
export default App;
Как вы можете видеть, состояние для приложения находится в компоненте приложения, который является компонентом класса. Если вы хотите иметь состояние внутри ваших компонентов, вам нужен компонент класса.
Таким образом, каждый компонент класса может иметь независимое состояние и может наследовать состояние от родительского компонента через реквизит. Это называется опорой и можно избежать с помощью контекста API.
Опора сверления
Опоротное бурение (также называемое «резьбой») относится к процессу, который вы должны пройти, чтобы получить данные в частях дерева компонентов React. Опоротное бурение На самом основном уровне просто явно передает значения по всему представлению вашего приложения.
Контекст API
Контекст API был введением в React версии 16.3.
Контекст обеспечивает способ передавать данные через дерево компонентов без необходимости передавать реквизиты вручную на каждом уровне.
Контекст предназначен для обмена данными, которые можно считать «глобальными» для дерева компонентов React, таких как текущий аутентифицированный пользователь, тема или предпочтительный язык.
Контекст API использует CreateContext () создать магазин, который удерживает контекст (состояние).
React.createContext
const MyContext = React.createContext(defaultValue);
Создает контекстный объект. Когда React делает компонент, который подписывается на этот объект контекста, он будет читать текущее значение контекста от ближайшего поставщика соответствующего над ним в дереве.
Контекст Провайдер
Каждый объект контекста поставляется с провайдером React Component, который позволяет потреблять компоненты подписываться на изменения контекста. Принимает значение Подтверждение будет передано потребляющим компонентам, которые являются потомками этого поставщика. Один поставщик может быть подключен ко многим потребителям. Поставщики могут быть вложены, чтобы переопределить значения глубже внутри дерева.
// Use the context decribed above
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
Контекст Потребитель
{value => /* render something based on the context value */}
Все потребители, которые являются потомками поставщика, будут повторно рендеринг всякий раз, когда изменяется ценность поставщика. Распространение от поставщика до его потомков потребителей (включая. ContextType и UseContext) не подлежит методу shopomponentupdate, поэтому потребитель обновляется, даже когда компонент предка пропускает обновление.
Код приложения с контекстом
Возвращаясь к нашему приложению, давайте использовать контекст API. Создать контекст Папка в папке SRC и добавьте файл index.js со следующим кодом:
src/context/index.js
import React, { Component } from 'react';
const apiURL = `https://jsonblob.com/api/jsonBlob/fddd0cec-8e0e-11ea-82f0-13fba022ad5b`;
const RecipeContext = React.createContext();
class RecipeProvider extends Component {
state = {
loading: true,
recipes: [],
search: '',
};
fetchRecipe = async () => {
const recipeData = await fetch(apiURL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
return response.json();
})
.catch((e) => console.error(e));
const { results } = await recipeData;
this.setRecipes(results);
this.setLoading(false);
};
setLoading = (loadingState) => this.setState({ loading: loadingState });
setRecipes = (list) => this.setState({ recipes: list });
componentDidMount() {
this.fetchRecipe();
}
render() {
return (
{this.props.children}
);
}
}
const RecipeConsumer = RecipeContext.Consumer;
export { RecipeProvider, RecipeConsumer, RecipeContext };
А теперь основной файл index.js будет выглядеть следующим образом:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { RecipeProvider } from './context/index';
ReactDOM.render(
,
document.getElementById('root')
);
И внутри app.js мы импортируем новый контекст Recelecontext Чтобы пропустить рецепты.
import React, { useContext } from 'react';
import './App.css';
import RecipeList from './components/RecipeList';
import { RecipeContext } from './context/index';
function App() {
const appContext = useContext(RecipeContext);
const { loading, recipes } = appContext;
return (
{loading ? (
loading recipes ...
) : (
)}
);
}
export default App;
Мы перемещаем компоненты в папке компонентов, файлы recipe.js и gecetelist.js.
Реагировать крючки
С React 16.8 мы можем использовать крючки, чтобы удерживать состояние также с функциональными компонентами.
Что такое функциональные компоненты?
В React есть два основных типа компонентов. Классовые компоненты и Функциональные компоненты Анкет Разница довольно очевидна. Классовые компоненты являются классами ES6 и Функциональные компоненты являются функциями . Единственное ограничение для функционального компонента – принять реквизит в качестве аргумента и вернуть действительный JSX.
Демонстрация, функциональный компонент
function Hello(props){
return Hello {props.name}
}
или же более простая версия
const Hello = ({name}) => Hello {name}
и вот тот же компонент, написанный как компонент класса
class Hello extends Component{
render(){
return Hello {this.props.name}
}
}
Что такое крючок?
Крюк – это особая функция, которая позволяет вам «зацепить» в функции React. Например, USESTATE это крючок, который позволяет добавлять состояние React в функциональные компоненты.
В функциональном компоненте у нас нет этого, поэтому мы не можем назначить или прочитать это . Вместо этого мы называем USESTATE Зацепите прямо внутри нашего компонента.
Что делает Usestate?
Он объявляет «переменную состояния» и функцию для обновления этой переменной. USESTATE это новый способ использовать то же самое, что и это. Штат предоставляет в классе. Обычно переменные «исчезают», когда функция выходит, но переменные состояния сохраняются в React.
Единственный аргумент в пользу usestate () крючком является начальное состояние. В отличие от занятий, государство не должно быть объектом.
USESTATE Крюк возвращает пару значений: текущее состояние и функция, которая обновляет его. Вот почему мы пишем const [count, (). Это похоже на это. STATE.Count и This.SetState в классе, за исключением того, что вы получаете их в пару.
В примере ниже переменная называется счет и функция обновления переменной – SetCount Анкет
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
Состояние начинается как {count: 0}, и мы увеличиваем переменную счета, когда пользователь нажимает кнопку, вызывая SetCount ().
И вы можете просто позвонить {count} Чтобы отобразить переменную.
Так USESTATE Позвольте нам добавить локальное состояние, чтобы реагировать функциональные компоненты, теперь давайте перейдем к другим крючкам.
Что делает использование?
Эффект крючка, Использоватьэффект , добавляет возможность выполнять побочные эффекты из функционального компонента. Он служит той же цели, что и ComponentDidMount, ComponentDidupDate и ComponentWillunMount в классах React, но объединяется в один API.
Используя этот крючок, вы говорите, что ваш компонент должен что -то сделать после рендеринга. React запомнит функцию, которую вы выполнили (мы назваем ее нашим «эффектом») и назовут ее позже после выполнения обновлений DOM. В этом эффекте мы установили заголовок документа, но мы также можем выполнить извлечение данных или вызвать какой -либо другой императивный API.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
Мы объявляем счет Состояние переменной, а затем мы говорим React, что нам нужно использовать эффект. Мы передаем функцию Использоватьэффект Крюк. Эта функция, которую мы выполняем, является нашим эффектом. В нашем эффекте мы установили заголовок документа, используя Document.title браузер API. Мы можем прочитать последние счет Внутри эффекта, потому что это в сфере нашей функции. Когда React делает наш компонент, он запомнит эффект, который мы использовали, а затем запустите наш эффект после обновления DOM. Это происходит для каждого рендеринга, включая первый.
Извлечение данных, настройка подписки и вручное изменение DOM в компонентах React – все это примеры побочных эффектов. Независимо от того, привыкли ли вы называть эти операции «побочные эффекты» (или просто «эффекты»), вы, вероятно, выполняли их в своих компонентах раньше.
Если бы мы хотели сделать тот же эффект с классовым компонентом, мы бы сделали это так:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
...
Теперь мы должны дублировать код между этими двумя методами жизненного цикла в классе. Это связано с тем, что во многих случаях мы хотим выполнить тот же побочный эффект независимо от того, установился ли компонент или был обновлен компонент.
Заполняется ли использование после каждого рендеринга? Да! По умолчанию он работает как после первого рендеринга, так и после каждого обновления.
Вместо того, чтобы думать с точки зрения «монтажа» и «обновления», вам может быть легче думать, что влияние происходит «после рендеринга». Реакция гарантирует, что DOM был обновлен к тому времени, когда он выполняет эффекты.
В некоторых случаях очистка или применение эффекта после каждого рендеринга может создать проблему производительности.
Повторное рендеринг повторно заполнит useeffect (), а приложение будет внутри цикла
Вы можете сказать, что реагировать на пропуск, применяя эффект, если определенные значения не изменились между повторными ресторанами. Чтобы сделать это, передайте массив в качестве дополнительного второго аргумента, чтобы Использоватьэффект :
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
Если вы используете эту оптимизацию, убедитесь, что массив включает все значения из области компонента (например, реквизит и состояние), которые изменяются с течением времени и которые используются эффектом.
Если вы хотите запустить эффект и почистить его только один раз (на креплении и разоблачении), вы можете пройти пустой массив ([]) в качестве второго аргумента. Это говорит о том, что ваш эффект не зависит от каких-либо значений от реквизита или состояния, поэтому ему никогда не нужно повторно запустить.
При прохождении [], поскольку второй аргумент ближе к знакомой компоненте и компоненту WillunMount Mental Model, обычно существуют лучшие решения, чтобы избежать повторного управления эффектами.
UseContext Hook
Принимает объект контекста (значение, возвращаемое из React.createContext) и возвращает текущее значение контекста для этого контекста. Текущее значение контекста определяется подразделением значения ближайшего над вызывающим компонентом в дереве.
const value = useContext(MyContext);
Компонент, вызывающий USECONTEXT, всегда будет повторно рендеринг при изменении значения контекста. Если повторное использование компонента стоит дорого, вы можете оптимизировать его, используя память.
Что такое запоминание?
Мемуализация – это мощная техника оптимизации, которая может значительно ускорить ваше применение, сохраняя результаты дорогих функций или компонент React и возвращая кэшированный результат, когда те же входы возникают снова.
Наш компонент все равно будет повторять, но React не будет повторно повторно подать детское дерево, если все Usememo Входные данные одинаковы.
function Button() {
let appContextValue = useContext(AppContext);
let theme = appContextValue.theme; // Your "selector"
return useMemo(() => {
// The rest of your rendering logic
return ;
}, [theme])
}
Или вы все еще можете оптимизировать рендеринг, используя React.memo. React.memo это Компонент более высокого порядка (компонент, который возвращает другой компонент). Это похоже на реагирование. PureComponent, но для функциональных компонентов вместо классов.
const ThemedButton = memo(({ theme }) => {
// The rest of your rendering logic
return ;
});
Другим способом использования памяти является использование:
UseCallback Hook
Возвращает замеченный обратный вызов.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
Передайте встроенный обратный вызов и множество зависимостей. UseCallback Вернут запоминающуюся версию обратного вызова, которая меняется только в том случае, если одна из зависимостей изменилась. Это полезно при передаче обратных вызовов оптимизированным дочерним компонентам, которые полагаются на справочное равенство, чтобы предотвратить ненужные рендеры (например, supcomponentupdate ).
Usememo Hook
Возвращает запоминаемое значение. Отличается от UseCallback
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Передайте функцию «создать» и множество зависимостей. Usememo будет пересекать запоминающуюся значение только тогда, когда одна из зависимостей изменилась. Эта оптимизация помогает избежать дорогостоящих расчетов на каждом рендеринге.
Помните, что функция, передаваемая в USEMEMO, работает во время рендеринга. Не делайте ничего там, что вы обычно не делаете во время рендеринга. Например, побочные эффекты принадлежат к использованию, а не USEMEMO. Если массив не будет предоставлен, новое значение будет рассчитано на каждом рендеринге.
UserEducer Hook
Альтернатива USESTATE Анкет Принимает редуктор типа (состояние, действие) = > NewState и возвращает текущее состояние в паре с методом отправки. (Если вы знакомы с Redux, вы уже знаете, как это работает.)
const [state, dispatch] = useReducer(reducer, initialArg, init);
userEducer обычно предпочтительнее использования, когда у вас есть сложная логика состояния, которая включает в себя несколько подведений или когда следующее состояние зависит от предыдущего.
Вот счетчик из раздела Usestate, переписанный для использования редуктора:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
);
}
React гарантирует, что отправка Функциональная идентичность стабильна и не изменится на повторных ресурсах.
Государство ленивая инициализация
Вы также можете создать начальное состояние лениво. Для этого вы можете передать функцию init в качестве третьего аргумента. Первоначальное состояние будет установлено на init (inityArg) . Он позволяет извлечь логику для расчета исходного состояния за пределами редуктора. Это также удобно для сброса состояния позже в ответ на действие:
function init(initialCount) {
return {count: initialCount};
}
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({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
);
}
Код приложения с React Hooks
Возвращаясь к нашему приложению рецепта, мы обновим файлы для использования крючков. Давайте обновим файл контекста index.js
src/context/index.js
import React, { useState, useEffect } from 'react';
const apiURL = `https://jsonblob.com/api/jsonBlob/fddd0cec-8e0e-11ea-82f0-13fba022ad5b`;
const RecipeContext = React.createContext();
const RecipeProvider = (props) => {
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
const fetchRecipe = async () => {
try {
const recipeData = await fetch(apiURL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const { results } = await recipeData.json();
setRecipes(results);
setLoading(false);
} catch (e) {
if (e) {
console.log(e.message, 'Try updating the API key in App.js');
}
}
};
useEffect(() => {
fetchRecipe();
}, []);
return (
{props.children}
);
};
const RecipeConsumer = RecipeContext.Consumer;
export { RecipeProvider, RecipeConsumer, RecipeContext };
Мы обновили Рецептэпровидер Компонент, чтобы быть функциональным компонентом, мы использовали новые крючки USESTATE и Использоватьэффект Чтобы обновить рецепты и переменные загрузки, и мы удалили методы SetRecipes и Setloading которые обновляли внутреннее состояние с this.setState () Анкет
А теперь отправляет объект, удерживающий переменные value = {{загрузка, рецепты}} Анкет
Построение магазина – рисунок Redux
Давайте обновим наше приложение для рецепта, чтобы иметь глобальный магазин. Сначала мы создаем Магазин папка.
Нам нужен редуктор
Мы создаем Reducer.js Файл в папке магазина.
import { SET_RECIPES, SET_ERROR } from './actionTypes';
const Reducer = (state, action) => {
switch (action.type) {
case SET_RECIPES:
return {
...state,
recipes: action.payload,
loading: false,
};
case SET_ERROR:
return {
...state,
error: action.payload,
loading: true,
};
default:
return state;
}
};
export default Reducer;
Мы создали функцию редуктора, которая принимает состояние и действие в качестве аргументов, предназначенных для доступа и управления глобальным состоянием приложения. Эта функция работает с соединением с собственным крюком React: userEducer () Анкет
ActionTypes
export const SET_RECIPES = 'SET RECIPES'; export const SET_ERROR = 'SET ERROR';
Мы создаем типы действий, как и рисунок Redux внутри actiontypes.js файл.
Нам нужен магазин
Чтобы создать глобальное состояние, нам нужен центральный магазин. Магазин представляет собой компонент высшего порядка (HOC), который содержит контекст (состояние).
Давайте создадим Store.js Файл в папке магазина.
import React, { createContext, useEffect, useReducer } from 'react';
import Reducer from './Reducer';
import { SET_RECIPES, SET_ERROR } from './actionTypes';
const initialState = {
recipes: [],
error: null,
loading: true,
};
const apiURL = `https://jsonblob.com/api/jsonBlob/fddd0cec-8e0e-11ea-82f0-13fba022ad5b`;
const StoreContext = createContext(initialState);
const Store = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const fetchRecipe = async () => {
try {
const recipeData = await fetch(apiURL, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
});
const { results } = await recipeData.json();
dispatch({ type: SET_RECIPES, payload: results });
} catch (error) {
if (error) {
console.log(error);
dispatch({ type: SET_ERROR, payload: error });
}
}
};
useEffect(() => {
fetchRecipe();
}, []);
return (
{children}
);
};
const StoreConsumer = StoreContext.Consumer;
export { Store, StoreConsumer, StoreContext };
Мы передаем начальный объект состояния по умолчанию и функцию редуктора, чтобы реагировать userEducer () В качестве аргументов деконструируют его ценности.
const [state, dispatch] = useReducer(Reducer, initialState);
Государство Значение указывает на Государственный объект и отправка Метод – это Функция редуктора Это управляет государством.
Затем мы передаем состояние состояния и отправлять в контекст.
Чтобы использовать магазин и получить доступ к своему глобальному состоянию из любой точки нашего приложения, нам нужно обернуть его вокруг нашего основного index.js файл. Теперь мы используем Магазин компонент из папки магазина.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Store } from './store/Store';
ReactDOM.render(
,
document.getElementById('root')
);
Внутри нашего App.js Подайте все дети компонента приложения иметь доступ к магазину и его значениям.
Это наш файл app.js:
import React, { useContext } from 'react';
import './App.css';
import RecipeList from './components/RecipeList';
import { StoreContext } from './store/Store';
function App() {
const appContext = useContext(StoreContext);
const { loading, recipes } = appContext[0];
return (
{loading ? (
loading recipes ...
) : (
)}
);
}
export default App;
Чтобы использовать {загрузка, рецепты} Мы должны изменить код:
const { loading, recipes } = appContext[0];
Потому что в поставщике мы отправляем массив с государством в качестве первого элемента Анкет
Спасибо, что просмотрели этот урок!
Оригинал: “https://dev.to/iliutastoica/react-context-api-hooks-redux-pattern-2p5l”