Глубоко изучите эффект Concent и всесторонне улучшите опыт разработки useEffect.

JavaScript React.js
Глубоко изучите эффект Concent и всесторонне улучшите опыт разработки useEffect.

❤ отметьте меня, если вам нравится концепт ^_^

Управление кодом побочных эффектов

Когда хуки еще не родились, у нас обычно есть встроенные функции жизненного цикла в классах.componentDidMount,componentDidUpdate,componentWillUnmountНапишите логику побочных эффектов.

Я не буду обсуждать это здесьcomponentWillUpdate,componentWillReceiveProps, так как с поддержкой асинхронного рендеринга в React эти функции были отмечены как небезопасные, давайте последуем тенденции истории и забудем о них напрочь 😀

Возьмем один из наиболее типичных сценариев применения следующим образом:

class SomePage extends Component{
    state = { products: [] }
    componentDidMount(){
        api.fetchProducts()
        .then(products=>this.setState({products}))
        .catch(err=> alert(err.message));
    }
}

Такой подобный код на 100% уверен, что вы его уже писали ранее, а смысл выражения очень прост: при первом монтировании компонента получить данные списка товаров.

Наша страница обычно выглядит так. Заголовок — это условная область ввода или выбора, а центральная большая область — это таблица. Теперь мы поместим некоторые требования на эту страницу. При изменении любого значения в области выбора будет запущен автоматический запрос Обновляйте список, делайте другие вещи, когда компонент уничтожен, уважаемые читатели должны иметь написанный код, подобный следующему:

class SomePage extends Component{
    state = { products: [], type:'', sex:'', addr:'', keyword:'' }
    
    componentDidMount(){
        this.fetchProducts();
    }
    
    fetchProducts = ()=>{
        const {type, sex, addr, keyword} = this.state;
        api.fetchProducts({type, sex, addr, keyword})
        .then(products=>this.setState({products}))
        .catch(err=> alert(err.message));
    }
    
    changeType = (e)=> this.setState({type:e.currentTarget.value})
    
    changeSex = (e)=> this.setState({sex:e.currentTarget.value})
    
    changeAddr = (e)=> this.setState({addr:e.currentTarget.value})
    
    changeKeyword = (e)=> this.setState({keyword:e.currentTarget.value})
    
    componentDidUpdate(prevProps, prevState){
        const curState = this.state;
        if(
            curState.type!==prevState.type ||
            curState.sex!==prevState.sex || 
            curState.addr!==prevState.addr || 
            curState.keyword!==prevState.keyword 
        ){
            this.fetchProducts();
        }
    }
    
    componentWillUnmount(){
        // 这里搞清理事情
    }
    
    render(){
        const { type, sex, addr, keyword } = this.state;
        return (
            <div className="conditionArea">
                <select value={type} onChange={this.changeType} >{/**some options here*/}</select>
                <select value={sex} onChange={this.changeSex}>{/**some options here*/}</select>
                <input value={addr} onChange={this.changeAddr} />
                <input value={keyword} onChange={this.changeKeyword} />
            </div>
        );
    }
}

Конечно, должны быть злющие подростки, которые не хотят так много писать.change***, отмеченный в узле рендерингаdata-***Для уменьшения кода вероятность такова:

class SomePage extends Component{
    changeKey = (e)=> this.setState({[e.currentTarget.dataset.key]:e.currentTarget.value})
    // 其他略...
    render(){
        const { type, sex, addr, keyword } = this.state;
        return (
            <div className="conditionArea">
                <select data-key="type" value={type} onChange={this.changeKey} >
                    {/**some options here*/}
                </select>
                <select data-key="sex" value={sex} onChange={this.changeKey}>
                    {/**some options here*/}
                </select>
                <input data-key="addr" value={addr} onChange={this.changeKey} />
                <input data-key="keyword" value={keyword} onChange={this.changeKey} />
            </div>
        );
    }
}

Если состояние этого компонента также необходимо принятьpropsчтобы обновить значение , затем используйте новую функцию в классеgetDerivedStateFromPropsзаменяет устаревшееcomponentWillReceiveProps, код записывается примерно так:

class SomePage extends Component{
    static getDerivedStateFromProps (props, state) {
        if (props.tag !== state.tag) return {tag: props.tag}
        return null
    }
}

Это мы завершилиclass组件Далее для обсуждения управления кодом побочных эффектов мы позволимhookПудровый дебют ━(`∀´)ノ亻!

крючок папа учит жизни

В начале зарождения хука приведенные выше аналогичные примеры использовались для раундов, а приведенные выше примеры были переписаны в более простые и понятные примеры, и их обучали за считанные минуты.class组件Будь человеком снова 😀

Посмотрим на переписанный код

const FnPage = React.memo(function({ tag:propTag }) {
  const [products, setProducts] = useState([]);
  const [type, setType] = useState("");
  const [sex, setSex] = useState("");
  const [addr, setAddr] = useState("");
  const [keyword, setKeyword] = useState("");
  const [tag, setTag] = useState(propTag);//使用来自props的tag作为初始化值

  const fetchProducts = (type, sex, addr, keyword) =>
    api
      .fetchProducts({ type, sex, addr, keyword })
      .then(products => setProducts(products))
      .catch(err => alert(err.message));

  const changeType = e => setType(e.currentTarget.value);
  const changeSex = e => setSex(e.currentTarget.value);
  const changeAddr = e => setAddr(e.currentTarget.value);
  const changeKeyword = e => setKeyword(e.currentTarget.value);

  // 等价于上面类组件里componentDidMount和componentDidUpdate里的逻辑
  useEffect(() => {
    fetchProducts(type, sex, addr, keyword);
  }, [type, sex, addr, keyword]);
  // 填充了4个依赖项,初次渲染时触发此副作用
  // 此后组件处于存在期,任何一个改变都会触发此副作用
  
  useEffect(()=>{
      return ()=>{// 返回一个清理函数
          // 等价于componentWillUnmout, 这里搞清理事情
      }
  }, []);//第二位参数传空数组,次副作用只在初次渲染完毕后执行一次1
  
  useEffect(()=>{
     // 首次渲染时,此副作用还是会执行的,在内部巧妙的再比较一次,避免一次多余的ui更新
     // 等价于上面组件类里getDerivedStateFromProps里的逻辑
     if(tag !== propTag)setTag(tag);
  }, [propTag, tag]);

  return (
    <div className="conditionArea">
      <select value={type} onChange={changeType}>
        {/**some options here*/}
      </select>
      <select data-key="sex" value={sex} onChange={changeSex}>
        {/**some options here*/}
      </select>
      <input data-key="addr" value={addr} onChange={changeAddr} />
      <input data-key="tkeywordype" value={keyword} onChange={changeKeyword} />
    </div>
  );
});

Выглядит так освежающе, нет дерева, кажется очень злым, чтобы писать? умное использованиеuseEffectЗаменена каждая функция жизненного цикла в компоненте класса, и нет путаницы в контекстеthis,настоящийфункционально ориентированныйПрограммирование!

Что еще более приятно, так это то, что хуки можно свободно комбинировать и вкладывать друг в друга, так что ваш выглядит толстым.FnPageЛогика может быть мгновенно уменьшена, поскольку

function useMyLogic(propTag){
    //刚才那一堆逻辑可以完全拷贝到这里,然后把状态和方法返回出去
    return {
      type, sex, addr, keyword, tag,
      changeType,changeSex,changeAddr, changeKeyword,
    };
}

const FnPage = React.memo(function({ tag: propTag }) {
  const {
    type, sex, addr, keyword, tag,
    changeType,changeSex,changeAddr, changeKeyword,
   } = useMyLogic(propTag);
  // return your ui
});

этоuseMyLogicФункции можно использовать где угодно! Как это будет удобно, если обновление статуса посложнее, официалка тоже поддерживаетuseReducerЧтобы отделить бизнес-логику от функции ловушки, следующий пример кода, приведенный Дэном Абрамовым:
Нажмите здесь, чтобы посмотреть онлайн-пример

const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'tick') {
    return { count: count + step, step };
  } else if (action.type === 'step') {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'tick' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={e => {
        dispatch({
          type: 'step',
          step: Number(e.target.value)
        });
      }} />
    </>
  );
}

😀 Говоря об этом, вы чувствуете, что у вас больше нет любви к классу? Но действительно ли идеально использовать хуки для организации бизнес-кода таким образом? Слабостей нет?

Используйте эффект Concent, чтобы обновить интерфейс useEffect.

useMyLogicЕго можно использовать повторно везде,useReducerЭто правда, что состояние снова отвязано и отделено от функции ловушки, но их проблемы заключаются в следующем:

  • Вопрос 1, по сути, хуки подталкивают разработчиков к использованию замыканий, потому что функция компонента хука создает область видимости, соответствующую этому моменту, в каждом кадре рендеринга, и различные состояния или методы, сгенерированные внутри области видимости, будут использоваться только для этого кадра. но чего мы не можем избежать, так это того, что каждый кадр рендеринга создает большое количество функций временного закрытия. Накопление оказывает дополнительное давление на js, требуя немедленного повторного использования. Можем ли мы этого избежать?Многократное создание временных замыкающих функцийКак насчет этих вопросов? Ответ да, конечно, пожалуйста, обратитесь к предыдущим статьям по конкретным причинам.Изменения, внесенные установкой, главное обсуждение здесьuseEffectи Концентаeffectсделать сравнение дляsetupЯ не буду повторяться здесь.
  • Вопрос 2,useReducerЭто только решает проблему разделения логики состояния обновления и функции ловушки, но это всего лишь чистая функция.Асинхронная логика не может быть написана в ней.Ваша асинхронная логика в конечном итоге окажется внутри пользовательской функции ловушки, иuseReducerЭто просто частичное управление состоянием, можем ли мы реализовать его?Обновления состояния могут быть асинхронными, синхронными, свободно компонуемыми, и их можно легко продвигать для целей глобального управления состоянием.Ну, ответ, конечно же, Concent'sinvokeИнтерфейс подскажет вам окончательный ответ!
  • Вопрос 3,useEffectЭто снимает критику управления кодом с побочными эффектами, но когда мы заменяем компоненты класса функциональными компонентами, требуется корректировка кода и логическое преобразование.Унифицируйте метод управления кодом побочных эффектов и разрешите совместное использование компонентов классов и функциональных компонентов без каких-либо изменений., ответ тоже вполне возможен, исходя изeffectИнтерфейс, вы можете добиться унифицированного управления побочными эффектами, не изменяя ни одной строки кода, а это значит, что ваши компоненты могут свободно переключаться между классами и функциями!

Резюмируем 3 проблемы, которые будут решены:

  • 1 Избегайте многократного создания временных замыкающих функций.
  • 2 Обновление состояния может быть асинхронным, синхронным, свободно комбинируемым, и его можно легко продвигать для целей глобального управления состоянием.
  • 3 Унифицируйте метод управления кодом побочных эффектов, позволив классам и функциям обеспечить безболезненное совместное использование без каких-либо затрат.

Давайте начнем шоу

Модернизация функциональных компонентов FnPage

Построить функцию настройки

const setup = ctx => {
  console.log('setup函数只会在组件初次渲染之前被执行一次');
  const fetchProducts = () => {
    const { type, sex, addr, keyword } = ctx.state;
    api.fetchProducts({ type, sex, addr, keyword })
      .then(products => ctx.setState({ products }))
      .catch(err => alert(err.message));
  };

  ctx.effect(() => {
    fetchProducts();
  }, ["type", "sex", "addr", "keyword"]);//这里只需要传key名称就可以了
  /** 原函数组件内写法:
    useEffect(() => {
      fetchProducts(type, sex, addr, keyword);
    }, [type, sex, addr, keyword]);
  */

  ctx.effect(() => {
    return () => {
      // 返回一个清理函数
      // 等价于componentWillUnmout, 这里搞清理事情
    };
  }, []);
  /** 原函数组件内写法:
    useEffect(()=>{
      return ()=>{// 返回一个清理函数
        // 等价于componentWillUnmout, 这里搞清理事情
      }
    }, []);//第二位参数传空数组,次副作用只在初次渲染完毕后执行一次
  */

  ctx.effectProps(() => {
    // 对props上的变更书写副作用,注意这里不同于ctx.effect,ctx.effect是针对state写副作用
    const curTag = ctx.props.tag;
    if (curTag !== ctx.prevProps.tag) ctx.setState({ tag: curTag });
  }, ["tag"]);//这里只需要传key名称就可以了
  /** 原函数组件内写法:
  useEffect(()=>{
    // 首次渲染时,此副作用还是会执行的,在内部巧妙的再比较一次,避免一次多余的ui更新
    // 等价于上面组件类里getDerivedStateFromProps里的逻辑
    if(tag !== propTag)setTag(tag);
  }, [propTag, tag]);
 */

  return {// 返回结果收集在ctx.settings里
    fetchProducts,
    //推荐使用此方式,把方法定义在settings里,下面示例故意直接使用sync语法糖函数
    changeType: ctx.sync('type'),
  };
};

Логика настройки построена, посмотрим как выглядит функциональная составляющая

import { useConcent } from 'concent';

//定义状态构造函数,传递给useConcent
const iState = () => ({ products:[], type: "", sex: "", addr: "", keyword: "", tag: "" });

const ConcentFnPage = React.memo(function(props) {
  // useConcent返回ctx,这里直接解构ctx,拿想用的对象或方法
  const { state, settings, sync } = useConcent({ setup, state: iState, props });
  const { products, type, sex, addr, keyword, tag } = state;
  const { fetchProducts } = settings;

  // 下面UI中使用sync语法糖函数同步状态,如果为了最求极致的性能
  // 可将它们定义在setup返回结果里,这样不用每次渲染都生成临时的更新函数
  return (
    <div className="conditionArea">
      <h1>concent setup compnent</h1>
      <select value={type} onChange={sync('type')}>
        <option value="1">1</option>
        <option value="2">2</option>
      </select>
      <select data-key="sex" value={sex} onChange={sync('sex')}>
        <option value="1">male</option>
        <option value="0">female</option>
      </select>
      <input data-key="addr" value={addr} onChange={sync('addr')} />
      <input data-key="keyword" value={keyword} onChange={sync('keyword')} />
      <button onClick={fetchProducts}>refresh</button>
      {products.map((v, idx)=><div key={idx}>name:{v.name} author:{v.author}</div>)}
    </div>
  );
});

Сила настройки заключается в том, что она выполняется только один раз перед первой визуализацией компонента, а возвращенные результаты собираются вsettings, что означает, что ваши API объявляются статически, а не воссоздаются каждый раз при рендеринге! В то же время вы можете определить другие функции в этом пространстве, такие какctx.onопределить прослушиватели событий,ctx.computedопределить функцию вычисления,ctx.watchОпределить функции наблюдения и т. д. Здесь мы сосредоточимся наctx.effect, другие методы использования могут ссылаться на следующие примеры:
код sandbox.IO/is/concent-a…
стек blitz.com/edit/concon…

Давайте посмотрим на эффект сейчас

Избегайте многократного создания временных функций закрытия

На данный момент мы решили первую задачу, т.е.Избегайте многократного создания временных функций закрытия

Тогда, если наша логика обновления состояния сопровождается множеством сложных операций, наше тело установки неизбежно будет становиться все более и более раздутым.Конечно, мы можем инкапсулировать и абстрагировать эти функции, и, наконец, вернуть результат, а затем вызватьctx.stateПерейти к обновлению, но в концентрат более элегантный интерфейсinvokeПозвольте вам сделать это, мы инкапсулируем эту логику в функции и помещаем их в файлlogic.js, затем вернитесь в новое состояние фрагмента, используяinvokeпозвони им

//code in logic.js

export function simpleUpdateType(type, moduleState, actionCtx){
    return { type };
}

Внутри тела установки вы можете создатьsettingsсвойства в функции вызывают функцию.

import * as lc from './logic';

const setup = ctx=>{
    //其他略
    return {
        upateType: e=> ctx.invoke(lc.simpleUpdateType, e.currentTarget.value);
    }
}

Это может показаться пустяком, разве это не просто вызов, давай, давайте изменим его на асинхронный способ записи

//code in logic.js
export async function complexUpdate(type, moduleState, actionCtx){
    await api.updateType(type);
    return { type };
}

// code in setup
import * as lc from './logic';

const setup = ctx=>{
    //其他略
    return {
        upateType: e=> ctx.invoke(lc.complexUpdate, e.currentTarget.value);
    }
}

Выглядит ли это более удобным? Что еще лучше, так это помочь нам написать несколько функций и свободно их комбинировать. Вы можете заметить, что список параметров функции не первый.payload, а второйmoduleState,третийactionCtx, если вызывающий объект не принадлежит ни к одному модулю, второй параметр — это объект без содержимого{}, когда он имеет значение, мы проанализируем его позже, здесь мы сосредоточимся на третьем параметреactionCtx, вы можете использовать его для объединения других функций, разве это не особенно удобно?

//code in logic.js
export async function complexUpdateType(type, moduleState, actionCtx){
    await api.updateType(type);
    return { type };
}

export async function complexUpdateSex(sex, moduleState, actionCtx){
    await api.updateSex(sex);
    return { sex };
}

export async function updateTypeAndSex({type, sex}, moduleState, actionCtx){
    await actionCtx.invoke(complexUpdateType, type);
    await actionCtx.invoke(complexUpdateSex, sex);
}

// code in setup
import * as lc from './logic';

const setup = ctx=>{
    //其他略
    return {
        upateType: e=> {
            // 为了配合这个演示,我们另开两个key存type,sex^_^
            const {tmpType, tmpSex} = ctx.state;
            ctx.invoke(lc.updateTypeAndSex, {type:tmpType, sex:tmpSex}};
        }
    }
}

Так что, если я хочу поделиться и изменить это состояние с другими компонентами? Нам нужно только сначала настроить состояние вrunВ функции (примечание z: чтобы использовать concent, вы должны вызвать функцию запуска перед рендерингом корневого компонента), она на самом деле используетuseConcentКогда имя модуля тега в порядке

Сначала настройте модуль

import { useConcent, run } from "concent";
import * as lc from './logic';

run({
    product:{
        //这里复用刚才的状态生成函数
        state: iState(), 
        // 把刚才的逻辑函数模块当做reducer配置在此处
        // 当然这里可以不配置,不过推荐配上,方便调用处不需要再引入logic.js
        reducer: lc,
    }
});

Затем добавьте тег модуля к компоненту иConcentFnPageНапротив, просто измените свойство состояния на модуль и установите для него значениеproduct

const ConcentFnModulePage = React.memo(function({ tag: propTag }) {
  // useConcent返回ctx,这里直接解构ctx,拿想用的对象或方法
  const { state, settings, sync } = useConcent({ setup, module:'product' });
  const { products, type, sex, addr, keyword, tag } = state;
  const { fetchProducts } = settings;
    
  //此处略,和ConcentFnPage 一毛一样的代码
  );
});

Внимание, оригиналConcentFnPageОн по-прежнему может работать нормально, не нужно менять ни одной строки кода, новыйConcentFnModulePageтакже просто используяuseConcentКогда значение модуля передается и состояние удаляется,ctx.stateВнедрить модуль-владелец, другой код включаетsetupВ организме нет изменения одной линии, но эффект от их работы разный.ConcentFnPageЭто бесмодульный компонент, и состояния его экземпляров изолированы.Например, если экземпляр 1 изменит состояние, это не повлияет на экземпляр 2, ноConcentFnModulePageБыть зарегистрированнымproductКомпонент модуля, что означает, что любой его экземпляр, который изменяет состояние, будет синхронизирован с другими экземплярами, и так легко повысить статус до общего! Отмечается только токен модуля.

Посмотрим на эффект! Уведомлениеconcent shared compСостояние двух экземпляров синхронизировано.

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

Унифицированное управление кодом побочных эффектов

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

Для Concent это еще проще.effectОн автоматически выполняет интеллектуальную адаптацию в соответствии с зарегистрированным типом компонента.Для компонентов класса адаптируются его различные функции жизненного цикла, а именноcomponentDidMount,componentDidMount,componentWillUnmount, адаптированный для функциональных компонентовuseEffect, поэтому такая же стоимость переключения равна 0 стоимости!

Переписанный компонент класса выглядит следующим образом, из него получается ctx, а прописанные параметры отдаются вregisterинтерфейс, обратите внимание.setupОн также непосредственно повторно используется.

class ConcentFnModuleClass extends React.Component{
  render(){
    const { state, settings, sync } = this.ctx;
    const { products, type, sex, addr, keyword, tag } = state;
    const { fetchProducts, fetchByInfoke } = settings;
  
    //此处略,一毛一样的代码
  }
}

export default register({ setup, module:'product' })(ConcentFnModuleClass);

Посмотрим на эффект!

Shared Comp — это функциональный компонент, а Shared Class Comp — это компонент класса.

Эпилог

Эта статья заканчивается здесь, я знаю Уважаемый, у вас должно быть много сомнений, или попробуйте сами, онлайн-пример приведенного выше фрагмента кода здесь,Добро пожаловать, нажмите, чтобы просмотреть, разветвить и изменить

Конечно, для вас также есть готовый пример стандартного шаблона кода.
js: код sandbox.IO/is/concent-a…
ts: код sandbox.IO/is/concent-a…

Когда человек достигает среднего возраста, жизнь непроста, облысение практически невозможно остановить, кодовое слово сложно, и судья, который его увидит, придет к ✨звезде.❤ отметьте меня, если вам нравится концепт ^_^

мы знаемhookРождение React улучшило опыт разработки React, поэтому дляConcentГоворя об этом, он делает гораздо больше, чем вы думаете.Разбиение и объединение кода, разделение и повторное использование логики, а также определение и совместное использование состояния могут сделать ваш опыт разработки вдвойне или более счастливым, потому чтоConcentСлоган一个可预测、0入侵、渐进式、高性能的增强型状态管理方案.

Для меня Хуафа уже начал падать, но если я смогу обменять больше развития ценой падения одного человека, я смогу сохранить эти черные и красивые густые волосы, я чувствую, что это того стоит в одно мгновение, ха-ха 😀