Используйте useContext для инкапсуляции собственного управления состоянием (дюжина строк кода).

React.js

начало

Я думаю, что для проекта со сложной логикой особенно важно управление состоянием.Хорошо ли управление состоянием или нет, напрямую зависит от того, являются ли логика, удобочитаемость и ремонтопригодность проекта ясной, легко читаемой и эффективной.

От самых ранних компонентов класса, использующих this.state, this.setState для управления состоянием, до публикации и подписки на избыточность, подписку, отправку, использование избыточности сталкивается с повторяющимися и тяжелыми редюсерами, что делает меня инженером Ctrl CV. Так что снова на связиdva, который основан наreduxа такжеredux-sagaСхема потока данных. Разделите общее состояние по модели, используйте метод Connect для передачи состояния на необходимые глубокие компоненты.

После того, как позже появились реагирующие хуки, в отрасли также появилось много самоуправляемых состояний, основанных на инкапсуляции хуков, каждый модуль имеет хранилище состояний, основанное на его собственных хуках. Он действительно решает управление состоянием функциональных компонентов и управление состоянием внутри самого модуля, но все же не может решить проблему, заключающуюся в том, что в глобальном компоненте зависимость состояния послойной передачи делает структуру сложной и громоздкой. Как мы можем общаться между компонентами без каких-либо инструментов управления?

Почему бы нет?

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

Что ты изучал?

Прочитав эту статью, даже если вы считаете, что мой стиль управления не очень хорош, вы сможете изучить и понять useMemo, useContext, useImmer и т. д.

react context

Context-ReactВведение на официальном сайте

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

createContext достигается в основном во время межкомпонентного взаимодействия

const MyContext = React.createContext(defaultValue);

<MyContext.Provider value={/* 某个值 */}>
    <App>
      ... 多层组件嵌套内有一个 Goods 组件
            <Goods />
  </App>  
</MyContext.Provider >


// 某个 子子子子子组件 Goods
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

Конкретные практические случаи

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State 也包含了更新函数,因此它会被传递进 context provider。
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // 整个 state 都被传递进 provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');

// 用户登录 context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // 提供初始 context 值的 App 组件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// 一个组件可能会消费多个 context
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

Инкапсулируйте собственное межкомпонентное управление

./connect.js файл

Инкапсулировать метод подключения

Использование connect также основано на идее react-redux и инкапсулирует ее как метод. Вызов метода connect возвращает компонент более высокого порядка. И метод подключения поддерживает передачу функции для фильтрации и фильтрации состояния, требуемого подкомпонентами, что также легко поддерживать, повторно отображать и т. д.

import React, { createContext } from 'react';
import { useImmer } from 'use-immer';
// useImmer 文章末尾有介绍推荐

const ctx = createContext();
const { Consumer, Provider } = ctx

const useModel = (initialState) => {
  const [state, setState] = useImmer(initialState);
  return [
    state,
    setState
  ];
}

const createProvider = () => {
  function WrapProvider(props) {
    const { children, value } = props;
    const [_state, _dispatch] = useModel(value)
    return (
      <Provider value={{
        _state,
        _dispatch,
      }}>
        {children}
      </Provider>
    )
  }
  return WrapProvider
}

export const connect = fn => ComponentUi => () => {
  return (
    <Consumer>
      {
        state => {
          const {_state, _dispatch} = state
          const selectState = typeof fn === 'function' ? fn(_state) : _state;
          return <ComponentUi _state={selectState} _dispatch={_dispatch} />
        }
      }
    </Consumer>
  )
};

export default createProvider;

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

import React from 'react';
import Header from './layout/Header.jsx';
import Footer from './layout/Footer.jsx';
import createProvider from './connect';

const Provider = createProvider()

const initValue = { user: 'xiaoming', age: 12 }
function App() {
  return (
    <Provider value={initValue}>
        <Header />
        <Footer />
    </Provider>

  )
}
export default App;

Header.jsx

import React from 'react';
import { Select } from 'antd';
import { connect } from '../connect';
const { Option } = Select;

function Head({ _state: { user, age }, _dispatch }) {
  return (
    <div className="logo" >
      <Select defaultValue={user} value={user} onChange={(value) => {
        _dispatch(draft => {
          draft.user = value
        })
      }}>
        <Option value='xiaoming'>小明</Option>
        <Option value='xiaohong'>小红</Option>
      </Select>
      <span>年龄{age}</span>
    </div>
  )
}

export default connect()(Head);

Footer.jsx

import React, { Fragment } from 'react';
import { Select } from 'antd';
import { connect } from '../../connect';

const { Option } = Select;
function Footer({ _state, _dispatch }) {
  const { user, age } = _state;
  return (
    <Fragment>
      <p style={{marginTop: 40}}>用户:{user}</p>
      <p>年龄{age}</p>
      <div>
        <span>改变用户:</span>
        <Select
          defaultValue={user}
          value={user}
          onChange={(value) => {
            _dispatch(draft => {
              draft.user = value
            })
          }}>
          <Option value='xiaoming'>小明</Option>
          <Option value='xiaohong'>小红</Option>
        </Select></div>
      <div>
        <span>改变年龄:</span>
        <input onChange={(e) => {
          // 这里使用 persist 原因可以看文章末尾推荐
          e.persist();
          _dispatch(draft => {
            draft.age = e.target.value
          })
        }} />
      </div>
    </Fragment>
  )
}

export default connect()(Footer);

использовать useContext

Мы все знаем, что useContext также выпущен после версии 16.8, поэтому мы можем оптимизировать метод подключения, используя useContext.

// 未使用 useContext
export const connect = (fn) => (ComponentUi) => () => {
  const state = useContext(ctx)
  console.log(state);
  return (
    <Consumer>
      {
        state => {
          const { _state, _dispatch } = state
          const selectState = typeof fn === 'function' ? fn(_state) : _state;
          return <ComponentUi _state={selectState} _dispatch={_dispatch} />
        }
      }
    </Consumer>
  )
};

// 使用 useContext
export const connect = fn => ComponentUi => () => {
  const { _state, _dispatch } = useContext(ctx);
  const selectState = typeof fn === 'function' ? fn(_state) : _state;
  return <ComponentUi _state={selectState} _dispatch={_dispatch} />;
};

Примечание: называетсяuseContextКомпонент всегда будет перерисовываться при изменении значения контекста. Если повторный рендеринг компонентов обходится дорого, вы можете узнать, как его оптимизировать, порекомендовав ненужный повторный рендеринг дорогостоящих компонентов в конце статьи.

наконец

гитхаб-адрес

Шаг 4 коды, чтобы запустить

git clone https://github.com/zouxiaomingya/blog
cd blog
npm i
npm start

Во всей статье, если есть какие-либо ошибки или неточности, обязательно исправьте их, спасибо!

Ссылаться на: