Redux против React Context API

Original author: Dave Ceddia
  • Translation
  • Tutorial


В React 16.3 был добавлен новый Context API. Новый в том смысле, что старый Context API был за кадром, большинство людей либо не знали о его существовании, либо не использовали, потому что документация советовала избегать его использования.

Однако теперь Context API является полноценной частью React, открытой для использования (не так, как раньше, официально).

Сразу после релиза React 16.3, появились статьи, в которых провозглашалась смерть Redux из-за нового Context API. Если бы вы спросили об этом у Redux, я думаю, он ответил бы — «сообщения о моей смерти сильно преувеличены».

В этом посте я хочу рассказать о том, как работает новый Context API, чем он похож на Redux, когда вы можете использовать Context вместо Redux и почему Context не заменяет Redux в каждом случае.

Если вам нужен просто обзор Context API, вы можете перейти по ссылке.

Пример React приложения


Я собираюсь предположить, что у вас есть понимание принципов работы с состоянием в React (props & state), но если этого нет, у меня есть бесплатный 5-дневный курс, который поможет вам узнать об этом.

Давайте посмотрим на пример, который подведет нас к концепции, используемой в Redux. Мы начнем с простой версии React, а затем посмотрим, как она выглядит в Redux и, наконец с Context.



В этом приложении информация о пользователе отображается в двух местах: на панели навигации в правом верхнем углу и на боковой панели рядом с основным контентом.

(Вы можете заметить, что большое сходство с Twitter. Не случайно! Один из лучших способов оттачивать ваши навыки React это — копирование (создание реплик существующих сайтов / приложений).

Структура компонента выглядит так:



Используя чистый React (только props) нам нужно сохранить информацию о пользователе достаточно высоко в дереве, чтобы она могла быть передана компонентам, которые в ней нуждаются. В этом случае информация о пользователе должна находиться в App.

Затем, чтобы передать информацию о пользователе тем компонентам, которые в ней нуждаются, приложению должно передать его в Nav и Body. Они, в свою очередь, передадут его UserAvatar (ура!) И Sidebar. Наконец, Sidebar должен передать ее в UserStats.

Давайте посмотрим, как это работает в коде (я помещаю все в один файл, чтобы сделать его более легким для чтения, но на самом деле это, вероятно, будет разделено на отдельные файлы, следуя какой-то стандартной структуре).

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const UserAvatar = ({ user, size }) => (
  <img
    className={`user-avatar ${size || ""}`}
    alt="user avatar"
    src={user.avatar}
  />
);

const UserStats = ({ user }) => (
  <div className="user-stats">
    <div>
      <UserAvatar user={user} />
      {user.name}
    </div>
    <div className="stats">
      <div>{user.followers} Followers</div>
      <div>Following {user.following}</div>
    </div>
  </div>
);

const Nav = ({ user }) => (
  <div className="nav">
    <UserAvatar user={user} size="small" />
  </div>
);

const Content = () => <div className="content">main content here</div>;

const Sidebar = ({ user }) => (
  <div className="sidebar">
    <UserStats user={user} />
  </div>
);

const Body = ({ user }) => (
  <div className="body">
    <Sidebar user={user} />
    <Content user={user} />
  </div>
);

class App extends React.Component {
  state = {
    user: {
      avatar:
        "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b",
      name: "Dave",
      followers: 1234,
      following: 123
    }
  };

  render() {
    const { user } = this.state;

    return (
      <div className="app">
        <Nav user={user} />
        <Body user={user} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));


Пример кода на CodeSandbox

Здесь App инициализирует state, содержащий объект “user”. В реальном приложении вы скорее всего извлечете эти данные с сервера и сохраните их в state для рендеринга.

Что касается пробрасывания props (“prop drilling”), это не страшно. Оно работает отлично. «Пробрасывание props'ов», это — идеальный образец работы React. Но пробрасывание в глубину дерева состояния может немного раздражать при написании. И раздражать все более, если вам приходится передавать множество props'ов (а не один).

Тем не менее, существует большой минус в этой стратегии: она создает связь между компонентами, которые связанными быть не должны. В приведенном выше примере Nav должен принять “user” prop и передать его в UserAvatar, даже если Nav в нем не нуждается.

Тесно связанные компоненты (например, те, которые передают props их детям), сложнее использовать повторно, так как вы должны привязать их к новым родителям всякий раз, когда вы используете их в новом месте.

Давайте посмотрим, как мы можем улучшить это.

Перед тем как использовать Context или Redux...


Если вы сможете найти способ объединить структуру своего приложения и воспользоваться преимуществами передачи props потомкам, это может сделать ваш код чище, без необходимости прибегать к глубокому пробросу props, Context, или Redux.

В этом примере children props — отличное решение для компонентов, которые должны быть универсальными, такими как Nav, Sidebar и Body. Также знайте, что вы можете передавать JSX в любой prop, а не только в children — поэтому, если вам нужно больше одного «слота» для подключения компонентов, помните об этом.

Вот пример React-приложения, в котором Nav, Body и Sidebar принимают дочерние элементы и отображают их как есть. Таким образом, тому, кто использует компонент не нужно беспокоиться о передаче определенных данных, которые требуется компоненту. Он может просто отобразить то, что ему нужно, по месту, используя данные, которые он уже имеет в области видимости. В этом примере также показано, как использовать любой prop для передачи детей.

(Спасибо Дэну Абрамову за это предложение!)

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const UserAvatar = ({ user, size }) => (
  <img
    className={`user-avatar ${size || ""}`}
    alt="user avatar"
    src={user.avatar}
  />
);

const UserStats = ({ user }) => (
  <div className="user-stats">
    <div>
      <UserAvatar user={user} />
      {user.name}
    </div>
    <div className="stats">
      <div>{user.followers} Followers</div>
      <div>Following {user.following}</div>
    </div>
  </div>
);

// Получаем children и рендерим их.
const Nav = ({ children }) => (
  <div className="nav">
    {children}
  </div>
);

const Content = () => (
  <div className="content">main content here</div>
);

const Sidebar = ({ children }) => (
  <div className="sidebar">
    {children}
  </div>
);

// Body должен содержать sidebar и content, но следуя такому подходу, 
// их может не быть.
const Body = ({ sidebar, content }) => (
  <div className="body">
    <Sidebar>{sidebar}</Sidebar>
    {content}
  </div>
);

class App extends React.Component {
  state = {
    user: {
      avatar:
        "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b",
      name: "Dave",
      followers: 1234,
      following: 123
    }
  };

  render() {
    const { user } = this.state;

    return (
      <div className="app">
        <Nav>
          <UserAvatar user={user} size="small" />
        </Nav>
        <Body
          sidebar={<UserStats user={user} />}
          content={<Content />}
        />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));


Пример кода на CodeSandbox

Если ваше приложение слишком сложное (сложнее, чем этот пример!), Может быть, сложно понять как адаптировать шаблон под children. Посмотрим, как вы можете заменить проброс props с помощью Redux.

Пример на Redux


Я быстро рассмотрю пример на Redux, чтобы мы могли глубже понять, как работает Context, поэтому, если у вас нет четкого понимания о работе Redux, сначала прочитайте мое введение в Redux (или посмотрите видео).

Вот наше React-приложение, переделанное для использования Redux. Информация о пользователе была перемещена в store Redux, а это означает, что мы можем использовать функцию react-redux connect для непосредственной передачи user prop в компоненты, которые в них нуждаются.

Это большая победа в плане избавления от связанности. Взгляните на Nav, Body и Sidebar, и вы увидите, что они больше не принимают и не передают user prop. Больше не играют в «горячую картошку» с props’ами. Больше никаких бесполезных связей.

Редьюсер здесь мало что делает; это довольно просто. У меня есть еще кое-что о том, как работают редьюсеры Redux и как писать иммутабельный код, который в них используется.

import React from "react";
import ReactDOM from "react-dom";

// Нам нужны функции createStore, connect, and Provider:
import { createStore } from "redux";
import { connect, Provider } from "react-redux";

// Создаем reducer с пустым объектом в качестве начального состояния.
const initialState = {};
function reducer(state = initialState, action) {
  switch (action.type) {
    // В ответ на action SET_USER изменяем state.   
    case "SET_USER":
      return {
        ...state,
        user: action.user
      };
    default:
      return state;
  }
}

// Создаем store с reducer'ом в качестве аргумента.
const store = createStore(reducer);

// Dispatch'им action для того чтоб задать user.
store.dispatch({
  type: "SET_USER",
  user: {
    avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b",
    name: "Dave",
    followers: 1234,
    following: 123
  }
});

// Это функция mapStateToProps, она извлекает один ключ из state (user) 
// и передает его как `user` prop.
const mapStateToProps = state => ({
  user: state.user
});

// Подключаем UserAvatar с помощью функции connect(), теперь он получает 
//`user` напрямую, без необходимости передавать от родительского компонента.

// вы можете разделить на 2 переменные:
//   const UserAvatarAtom = ({ user, size }) => ( ... )
//   const UserAvatar = connect(mapStateToProps)(UserAvatarAtom);
const UserAvatar = connect(mapStateToProps)(({ user, size }) => (
  <img
    className={`user-avatar ${size || ""}`}
    alt="user avatar"
    src={user.avatar}
  />
));

// Также подключаем UserStats с помощью функции connect(), теперь он получает 
// `user` напрямую.
const UserStats = connect(mapStateToProps)(({ user }) => (
  <div className="user-stats">
    <div>
      <UserAvatar />
      {user.name}
    </div>
    <div className="stats">
      <div>{user.followers} Followers</div>
      <div>Following {user.following}</div>
    </div>
  </div>
));

// Теперь у компонента Nav больше нет необходимости знать о `user`.
const Nav = () => (
  <div className="nav">
    <UserAvatar size="small" />
  </div>
);

const Content = () => (
  <div className="content">main content here</div>
);

// Как и Sidebar.
const Sidebar = () => (
  <div className="sidebar">
    <UserStats />
  </div>
);

// Как и Body.
const Body = () => (
  <div className="body">
    <Sidebar />
    <Content />
  </div>
);

// В App теперь не храниться состояния, он может быть чистой функцией.
const App = () => (
  <div className="app">
    <Nav />
    <Body />
  </div>
);

// Обернем все приложение в Provider, 
// теперь у connect() есть доступ к store.
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")
);


Пример кода на CodeSandbox

Теперь вам наверно интересно, как Redux достигает этой магии. Удивительно. Как React не поддерживает передачу props на несколько уровней, а Redux может это сделать?

Ответ заключается в том, что Redux использует context функцию React (context feature). Не современный Context API (еще нет), а старый. Тот, который в документации React сказано не использовать, если вы не пишете свою библиотеку или не знаете, что делаете.

Контекст похож на компьютерную шину, идущую за каждым компонентом: чтобы получить мощность (данные), проходящую через нее, вам нужно только подключиться. И react-redux connect делает именно это.

Однако эта особенность Redux — лишь верхушка айсберга. Передача данных лишь в необходимое место является наиболее очевидной из особенностей Redux. Вот несколько других преимуществ, которые вы получаете из коробки:

connect – чистая функция

connect автоматически делает подключенные компоненты «чистыми», то есть они будут повторно рендериться только при изменении их props — тоесть, когда изменяется их срез состояния Redux. Это предотвращает ненужный ре-рендер и ускоряет работу приложения.

Легкая отладка с помощью Redux

Церемония написания actions и reducers уравновешивается удивительной легкостью отладки, которую Redux вам предоставляет.

С расширением Redux DevTools вы получаете автоматический журнал всех actions, выполняемых вашим приложением. В любое время вы можете открыть его и посмотреть, какие actions были запущены, какой у них payload, и state до и после action’а.



Еще одной замечательной возможностью, которую предоставляет Redux DevTools является отладка c помощью «путешествий во времени», тоесть вы можете нажать на любой предыдущий action, и перейти к этому моменту времени, вплоть до текущего. Причина, по которой это работает, состоит в том, что каждый action одинаково обновляет store, поэтому вы можете взять список записанных обновлений состояния и воспроизвести их без каких-либо побочных эффектов, и закончить в нужном вам месте.

Также есть такие инструменты, как LogRocket, которые в основном дают вам постоянно действующий Redux DevTools в продакшене для каждого из ваших пользователей. Получили bug report? Не проблема. Посмотрите эту сессию пользователя в LogRocket, и вы можете увидеть повторение того, что он сделал, и какие именно actions были запущены. Все это работает, используя поток action’ов Redux.

Расширяем Redux с Middleware

Redux поддерживает концепцию middleware (причудливое слово, обозначающее «функцию, которая запускается каждый раз при отправке action’а»). Написание собственной middleware не так сложно, как может показаться, и позволяет использовать некоторые мощные средства.

Например…

  • Хотите посылать API-запрос каждый раз, когда имя action’a начинается с FETCH_? Вы можете сделать это с помощью middleware.
  • Хотите централизованное место для логирования событий в вашем аналитическом ПО? Middleware — хорошее место для этого.
  • Хотите предотвратить запуск action’a в определенный момент времени? Вы можете сделать это с помощью middleware, невидимого для остальной части вашего приложения.
  • Хотите перехватить action, имеющий токен JWT, и автоматически сохранить его в localStorage? Да, middleware.

Вот хорошая статья с примерами того, как писать Redux middleware.

Как использовать React Context API


Но может быть, вам не нужны все эти странности Redux. Возможно, вам не нужна простая отладка, настройка или автоматическое улучшение производительности — все, что вы хотите сделать, это легко передавать данные. Может быть, ваше приложение мало, или вам просто нужно быстро что-то сделать а разбираться с тонкостями позже.

Новый Context API, вероятно, подойдет вам. Давайте посмотрим, как он работает.

Я опубликовал быстрый урок по Context API на Egghead, если вам больше нравиться смотреть, чем читать (3:43).

Вот 3 важных составляющих Context API:

  • Функция React.createContext, которая создает context
  • Provider (возвращается createContext), который устанавливает «электрическую шину»,
    проходящую через дерево компонентов
  • Consumer (также возвращается createContext), который впитывается в
    «электрическую шину» для извлечения данных

Provider очень похож на Provider в React-Redux. Он принимает значение, которое может быть всем, чем хотите (это может быть даже store Redux… но это было бы глупо). Скорее всего, это объект, содержащий ваши данные и любые actions, которые вы хотите выполнить с данными.

Consumer работает немного похоже как функция connect в React-Redux, подключаясь к данным, и сделав их доступными для компонента, который их использует.

Вот основные моменты:

// Для начала создаем новый context
// Это объект с 2 свойствами: { Provider, Consumer }
// Заметим, что они именованы, используя UpperCase, не camelCase
// Это важно, так как мы будем использовать из как компоненты в дальнейшем,
// а имена компонентов должны начинаться с большой буквы.
const UserContext = React.createContext();

// Компоненты, которым необходимы данные из context,
// используют свойство Consumer. 
// Consumer использует паттерн "render props".
const UserAvatar = ({ size }) => (
  <UserContext.Consumer>
    {user => (
      <img
        className={`user-avatar ${size || ""}`}
        alt="user avatar"
        src={user.avatar}
      />
    )}
  </UserContext.Consumer>
);

// Подчеркну, что нам больше не нужен "user prop",
// так как Consumer получает его из context.
const UserStats = () => (
  <UserContext.Consumer>
    {user => (
      <div className="user-stats">
        <div>
          <UserAvatar user={user} />
          {user.name}
        </div>
        <div className="stats">
          <div>{user.followers} Followers</div>
          <div>Following {user.following}</div>
        </div>
      </div>
    )}
  </UserContext.Consumer>
);

// ... здесь остальные компоненты ...
// ... (им больше не нужно беспокоиться о `user`).

// Внутри App мы передаем context вниз, используя Provider.
class App extends React.Component {
  state = {
    user: {
      avatar:
        "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b",
      name: "Dave",
      followers: 1234,
      following: 123
    }
  };

  render() {
    return (
      <div className="app">
        <UserContext.Provider value={this.state.user}>
          <Nav />
          <Body />
        </UserContext.Provider>
      </div>
    );
  }
}


Пример кода на CodeSandbox

Давайте рассмотрим, как это работает.

Помните, у нас есть 3 части: сам context (созданный с помощью React.createContext) и два компонента, которые с ним взаимодействуют (Provider и Consumer).

Provider и Consumer работают вместе

Provider и Consumer связаны друг с другом и неотделимы. Они знают только, как взаимодействовать друг с другом. Если вы создали два отдельных context, скажем, «Сontext1» и «Сontext2», тогда Provider и Consumer Сontext1 не смогут общаться с Provider’ом и Consumer’ом Context2.

Контекст не содержит state

Обратите внимание, что context не имеет собственного state. Это всего лишь канал для ваших данных. Вы должны передать значение в Provider, и это значение передастся в любой Consumer, который знает, как его искать (Provider, привязан к тому же контексту, что и Consumer).

Когда вы создаете context, вы можете передать «значение по умолчанию» следующим образом:

const Ctx = React.createContext(yourDefaultValue);


Значение по умолчанию это — то, что получит Consumer, когда будет помещен в дерево без Provider’a над ним. Если вы не передадите его, значение будет undefined. Обратите внимание, что это значение по умолчанию, а не начальное значение. Сontext ничего не сохраняет; он просто распространяет данные, которые вы в него передаете.

Consumer использует паттерн Render Props

Функция connect Redux это — компонент высшего порядка (сокращенно HoC). Она обворачивает другой компонент и передает в него props.

Consumer, напротив, ожидает, что дочерний компонент будет функцией. Затем он вызывает эту функцию во время рендеринга, передавая значение, полученное им от Provider’a где-то над ним (или значение по умолчанию из context, или undefined, если вы не передали значение по умолчанию).

Provider принимает одно значение

Просто одно значение, как prop. Но помните, что значение может быть любым. На практике, если вы хотите передать несколько значений вниз, вы должны создать объект со всеми значениями и передать этот объект вниз.

Context API гибкий


Поскольку создание контекста дает нам два компонента для работы с (Provider и Consumer), мы можем использовать их как мы хотим. Вот пара идей.

Оберните Consumer в HOC

Не нравится идея добавления UserContext.Consumer вокруг каждого места, которое в нем нуждается? Это ваш код! Вы вправе решать, что для вас будет лучшим выбором.

Если вы предпочитаете получать значение в качестве prop, вы можете написать небольшую обертку вокруг Consumer’a следующим образом:

function withUser(Component) {
  return function ConnectedComponent(props) {
    return (
      <UserContext.Consumer>
        {user => <Component {...props} user={user}/>}
      </UserContext.Consumer>
    );
  }
}

После этого, вы можете переписать, например UserAvatar с использованием функции withUser:

const UserAvatar = withUser(({ size, user }) => (
  <img
    className={`user-avatar ${size || ""}`}
    alt="user avatar"
    src={user.avatar}
  />
));

И вуаля, context может работать так же, как connect Redux’a. Минус автоматическая чистота.

Вот пример CodeSandbox с этим HOC.

Держите State в Provider’e

Помните, что Provider — это просто канал. Он не сохраняет никаких данных. Но это не мешает вам сделать свою собственную обертку для хранения данных.

В приведенном выше примере данные хранятся в App, так что единственное, что вам нужно было понять, это компоненты Provider + Consumer. Но, возможно, вы хотите создать свой собственный store. Вы можете создать компонент для хранения состояния и передачи их через контекст:

class UserStore extends React.Component {
  state = {
    user: {
      avatar:
        "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b",
      name: "Dave",
      followers: 1234,
      following: 123
    }
  };

  render() {
    return (
      <UserContext.Provider value={this.state.user}>
        {this.props.children}
      </UserContext.Provider>
    );
  }
}

// ... пропускаем среднюю часть ...

const App = () => (
  <div className="app">
    <Nav />
    <Body />
  </div>
);

ReactDOM.render(
  <UserStore>
    <App />
  </UserStore>,
  document.querySelector("#root")
);

Теперь данные о пользователе содержатся в своем собственном компоненте, единственной задачей которого являются эти данные. Круто. App снова может стать чистым (stateless). Я думаю, что это и выглядит немного чище.

Вот пример CodeSandbox с этим UserStore.

Прокидываем actions вниз через context

Помните, что объект, передаваемый через Provider, может содержать все, что вы хотите. Это означает, что он может содержать функции. Вы можете даже назвать их actions.

Вот новый пример: простая комната с выключателем для переключения цвета фона — ой, я имею в виду света.



State хранится в store, который также имеет функцию переключения света. Как state, так и функция передаются через контекст.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

// Пустой context.
const RoomContext = React.createContext();

// Компонент, задачей которого является управление 
// светом в комнате.
class RoomStore extends React.Component {
  state = {
    isLit: false
  };

  toggleLight = () => {
    this.setState(state => ({ isLit: !state.isLit }));
  };

  render() {
    // Передаем state и onToggleLight action
    return (
      <RoomContext.Provider
        value={{
          isLit: this.state.isLit,
          onToggleLight: this.toggleLight
        }}
      >
        {this.props.children}
      </RoomContext.Provider>
    );
  }
}

// Получаем информацию о том, светло ли в комнате, 
// и функцию для переключения света из RoomContext.
const Room = () => (
  <RoomContext.Consumer>
    {({ isLit, onToggleLight }) => (
      <div className={`room ${isLit ? "lit" : "dark"}`}>
        The room is {isLit ? "lit" : "dark"}.
        <br />
        <button onClick={onToggleLight}>Flip</button>
      </div>
    )}
  </RoomContext.Consumer>
);

const App = () => (
  <div className="app">
    <Room />
  </div>
);

// Оборачиваем все приложение в RoomStore,
// это будет работать так же хорошо как и в прошлом примере.
ReactDOM.render(
  <RoomStore>
    <App />
  </RoomStore>,
  document.querySelector("#root")
);

Вот полный рабочий пример в CodeSandbox.

Так все-таки, что использовать, Context или Redux?

Теперь, когда вы видели оба пути — какой из них стоит использовать? Я знаю, что вы хотите просто услышать ответ на данный вопрос, но я вынужден ответить — «зависит от вас».

Это зависит от того, насколько велико ваше приложение сейчас или насколько быстро будет расти. Сколько людей будет работать над ним — только вы или большая команда? Насколько опытны вы или ваша команда в работе с функциональными концепциями, на которые полагается Redux (такими как иммутабельность и чистые функции).

Пагубной ошибкой, пронизывающей всю экосистему JavaScript, является идея конкуренции. Есть идея, что каждый выбор — игра с нулевой суммой: если вы используете библиотеку A, вы не должны использовать ее конкурента библиотеку Б. Что когда выходит новая библиотека, которая чем-то лучше предыдущей, она должна вытеснить существующую. Что все должно быть или / или, что вы должны либо выбрать самое новое и лучшее, либо быть отодвинутым на задний план вместе с разработчиками из прошлого.

Лучший подход — посмотреть на этот замечательный выбор на примере, набора инструментов. Это похоже на выбор между использованием отвертки или мощного шуруповерта. Для 80% случаев шуруповерт выполнит работу легче и быстрее, чем отвертка. Но для других 20% отвертка будет лучшим выбором (мало места, или предмет тонкий). Когда я купил шуруповерт, я не сразу выбросил отвертку, он не заменил ее, а просто дал мне еще один вариант. Другой способ решить проблему.

Context не «заменяет» Redux, не более, чем React «заменил» Angular или jQuery. Черт, я все еще использую jQuery, когда мне нужно что-то сделать быстро. Я по-прежнему иногда использую серверные шаблоны EJS вместо того, чтобы развернуть приложение React. Иногда React — больше, чем вам нужно для выполнения задачи. То же самое касается и Redux.

Сегодня, если Redux — больше, чем вам нужно, вы можете использовать context.

Обучение React может быть тяжелым — так много библиотек и инструментов!

Мой совет? Игнорировать их все :)

Для пошагового обучения прочитайте мою книгу «Pure React».
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 24

    0
    connect автоматически делает подключенные компоненты «чистыми», то есть они будут повторно рендериться только при изменении их props — тоесть, когда изменяется их срез состояния Redux. Это предотвращает ненужный ре-рендер и ускоряет работу приложения.



    Если посмотреть на код библиотеки то можно легко убедиться что все что там делается это проверка объекта состояния на равенство. Поэтому чтобы redux работал нужно применить Object.assign или сейчас деструктивное присваивание. При этом естественно объект полученный будет новым только для свойств первого уровня вложенности. А все все дочерние объекты по прежнему присутствовать в виде ссылокна существующую или в терминологии автора грязные обьекты. Поэтому у когда начинаются построения на эту тему с глубокой философией кажутся неубедительными
      0
      А все все дочерние объекты по прежнему присутствовать в виде ссылок на существующую или в терминологии автора грязные обьекты

      Предполагается, что если вы мутируете ваши данные redux-store, то вы ССЗБ со всеми вытекающими. Или я неправильно вас понял?

        0
        Вашу аббревиатуру не понимаю. Я имею в иду только то что сказал. В redux это одна строчка кода проверка на равенство объектов и никакой дополнительной магии. И все построения о чистоте получаемых объектов это просто какой то штамп который все пафосно повторяют.
          +2

          Какой штамп? Какой пафос? О чём вы? Этой "одной строки кода" по shallow-проверке более чем достаточно. Никакой проверки в глубину не требуется. В рамках работы с redux все изменения в store должны порождать новые объекты по всей иерархии древа данных. Иммутабельность. Если вы работая с redux поступаете иначе, то выбросьте redux и возьмите, скажем, mobx. Зачем использовать библиотеку вопреки логике её работы?


          Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state.
            0
            Но вы согласны что Коннект не делается цитирую автоматически объекты чистыми. Это делает кто-то другой — разработчикф
            В вашей цитате все верно сказано что редьючкр чистая функция что не одно и то же с тем что она делает объекты чистыми
              +3

              Да. Это делает разработчик. С этим согласен. Давайте копнём в вашу цитату:


              connect автоматически делает подключенные компоненты «чистыми», то есть они будут повторно рендериться только при изменении их props

              Вы, возможно, незнакомы с React.PureComponent. Это те самые "чистые" компоненты, о которых идёт речь. Отличаются они только shallow-проверкой props в shouldComponentUpdate, о которой как раз и идёт речь.


              Можно ли называть такие компоненты "чистыми" — тема для отдельного спора. На мой взгляд, название было выбрано очень неудачным. shallow-проверка не гарантирует чистоту объекта. Да и вообще с точки зрения функционального программирования React взаимодействия не являются чистыми, об этом на хабре уже было много-много споров. Никакой "чистотой" там и не пахнет.


              Но! Название у PureComponent именно такое. Так что автор прав, connect-обёртка делая shallow-проверку делает компонент "чистым", в терминологии React-а. И да, это приводит к тому, о чём пишет автор — rerender будет только в случае, если в props были изменения (на верхнем уровне).


              Никакого пафоса тут нет. Это всё именно так и работает. Именно такая терминология задана авторами redux и react.


              А какие-либо споры на тему: "если я мутирую store, то оно сломается" мне кажутся таким же бессмысленными,


              как и вот это

                +1
                А кто и где говорил про чистые объекты-то? Я вижу только упоминание чистых компонентов. Чистый компонент — это термин React, и он означает «компонент который перерисовывается тогда и только тогда, когда меняются его свойства или состояние».
        0
        И ещё. Redux скорее всего будет постепенно входить в противоречие с новым функционалом с отложенным рендерингом. И именно это может стать причиной выхода его из употребления. Но об этом рано ещё говорить ТК и сам функционал ещё в разработке если я не пропустил его релиз. Да redux может быть будет адаптирован под новый функционал. Ведь и к тому и другому проекту имеет прямое отношение Д. Абрамов
          +1
          Я всегда воспринимал Context как возможность инъекции глобальных зависимостей (что-то вроде DI Container'a), а не как аналог библиотеки управления данными. Просто чтобы не было такой фигни, как сейчас в редаксе, что у тебя есть отдельная глобальная переменная, к которой ты обращаешься через процедуру connect. Вроде, этот Context API именно под такое и создавался
            0

            Старый context-api был статичным by design, readonly. Новый вполне себе реактивный. Вроде как задуман для сражения с ветр...props hell.

            +1

            Контексты – способ передачи параметров сквозь уровни иерархии компонентов. Redux – инструмент управления состоянием.


            Почему они противопоставляются?

            • UFO just landed and posted this here
              0

              Статья будет неполной без ответа Дэна Абрамова, где он показывает третий способ, как можно расшарить информацию о пользователе, не используя контекст:


              <div className="app">
                <Nav>
                  <UserAvatar user={user} size="small" />
                </Nav>
                <Body 
                  sidebar={<UserStats user={user} />} 
                  content={<Content />} 
                />
              </div>
                +2
                Судя по твиту он предлагает вручную инджектить все зависимости во всё дерево. Класс, Абрамов, ты в своем духе.
                  0
                  Ну а чо? Слабая связность для слабаков.
                +2

                Мне понравилось то как действия и состояние устроены в vuex — оно продуманно идеально, и сделано очень удобно.


                Самое близкое что есть в React это mobx-state-tree правда обращение к другим узлам дерева состояний в нем сделано через ж не совсем тривиально и заставляет страдать. Но один фиг оно удобнее чем километровые портянки action+reducer.


                Что касается контекста его синтаксис меня угнетает, я согласен что он скорее для DI чем для данных

                  0
                  Очень удобно работать в данными в несколько прислали graphql/apollo без redux. Особенно если необходимо реализовать изоморфные приложение с серверные и клиентским рендерингом
                    +1

                    Согласен — я пробовал связывать мутации и объекты через redux-thunk в итоге отрастил бороду и 3 раза умер, потом плюнул и переписал все на mobx-state-tree.


                    Можно конечно вообще через тот же ApolloClient + HOC но в моем случае это вылилось в особый извращенный генератор который вытаскивал scheme интроспекцией и генерировал HOC на лету. Решение было хорошим, пока объектов немного но с ростом проекта эта штука стала серьезно так кушать ресурсы.

                      +1

                      юзайте сагу наконец

                  0
                  Попробовал на небольшом приложении использовать новый context api. В итоге получил следующую проблемку… когда компоненту нужна информация из нескольких контекстов… как это красиво писать? Потому что вышло что-то вроде этого:

                  //Root.js
                  import React from 'react';
                  
                  import { Context1Provider } from './contexts/context1.js';
                  import { Context2Provider } from './contexts/context2.js';
                  import { Context3Provider } from './contexts/context3.js';
                  
                  import App from './App.js';
                  
                  
                  const Root = () => 
                    <Context1Pprovider>
                    <Context2Pprovider>
                    <Context3Pprovider>
                      <App />
                    </Context1Pprovider>
                    </Context2Pprovider>
                    </Context3Pprovider>
                  
                  
                  export default Root;
                  


                  //где-то в глубине дерева App.js некий компонент SomeComponent.js,
                  //который нуждается в информации от первого и третьего контекста:
                  
                  import React from 'react';
                  import { Context1 } from './contexts/context1.js';
                  import { Context3 } from './contexts/context3.js';
                  
                  class SomeComponent extends React.Component {
                  
                    render(){
                      return(
                        <div className="...">
                          <div>... some markup ...</div>
                          <div>... some markup ...</div>
                  
                          <Context1.Consumer>{data=>{
                            return <span>data.someValueFromContext1</span>
                          }}</Context1.Consumer>
                  
                          <div>... some markup ...</div>
                          <div>... some markup ...</div>
                  
                          <Context3.Consumer>{data=>{
                            return <span>data.someValueFromContext3</span>
                          }}</Context3.Consumer>
                  
                          <div>... some markup ...</div>
                        </div>
                      );
                    }
                  
                  }
                  
                  export default SomeComponent;
                  


                  Кто-то сталкивался?
                    0

                    Можно в виде декоратора:


                    @context(Consumer1, Consumer2, Consumer3)
                    render(val1, val2, val3)
                    {
                    }

                    Ну и можно в виде HOC-а, и работать просто с props.

                      0

                      https://reactjs.org/docs/context.html#consuming-multiple-contexts


                      If two or more context values are often used together, you might want to consider creating your own render prop component that provides both.

                      Код
                      const ComposeContext1And3 = (props) => (
                          <Context1.Consumer>
                            {data1 => (
                              <Context2.Consumer>
                                {data2 => (
                                  props.children({ data1, data2 })
                                )}
                              </Context2.Consumer>
                            )}
                          </Context1.Consumer>
                      )
                      
                      class SomeComponent extends React.Component {
                      
                        render() {
                          return (
                            <ComposeContext1And3>
                              {({ data1, data2 }) => (
                                <div className="...">
                                  <div>... some markup ...</div>
                                  <div>... some markup ...</div>
                      
                                  <span>data1.someValueFromContext1</span>
                      
                                  <div>... some markup ...</div>
                                  <div>... some markup ...</div>
                      
                                  <span>data2.someValueFromContext3</span>
                      
                                  <div>... some markup ...</div>
                                </div>
                              )}
                            </ComposeContext1And3>
                          );
                        }
                      }

                      Или уже сделали хелперы


                        0
                        Супер, спасибо!
                      0
                      Я чего-то не понимаю или плохо понял работу контекст апи, но почему нельзя просто сделать так?

                      const AppContext = React.createContext();

                      <AppContext.Provider value={{Context1, Context2, Context3}}>
                      ...children
                      </AppContext.Provider>


                      или же если нужна некая производительность то сделать HOC в который можно передавать наблюдателя с каким именно контекстом работать

                      Only users with full accounts can post comments. Log in, please.