Чтобы бросить вызов настройке Vue3, Concent объединяет усилия с React!

внешний интерфейс JavaScript React.js
Чтобы бросить вызов настройке Vue3, Concent объединяет усилия с React!

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

Управляемое чтение

Закончил статью в прошлом номереСоздание компонента и обновление статуса операции concent SaoПосле этого в конце были оставлены следующие два примечания к статье.Согласно первоначальному примечанию, заголовок этой статьи должен был быть [Исследование изменений, вызванных установкой], но поскольку эта статья действительно будетvue3внутреннийsetupособенности иConcentДля сравнения, я временно изменил название на [Challenge Vue3 setup, Concent и React сделали ход! ], чтобы отразитьsetupОсобенности, ваше React-приложение станет острым, организация кода будет иметь больший простор для воображения, конечно, согласитесь, немного, наверное, я видел это в июне.Vue Function-based API RFCЭта статья меня очень вдохновила. До этого у меня всегда была идея. Если я хочу унифицировать работу по сборке функциональных компонентов и компонентов класса, мне нужно определить входной API, но название, похоже, не определено до этой статьи. упомянутьsetupПосле этого я был совершенно просветлен, работа, которую он проделал, была, по сути, такой же, как и эффект, которого я хотел достичь! такConcentвнутреннийsetupТак родилась эта функция.

Прежде чем мы начнем, давайте просмотрим пример рабочей установки, чтобы показать, что это стандартная функция, доступная в рабочей среде.Войдите в онлайн-среду IDE

Мотивация дизайна установки Vue3

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

  • Источник данных в шаблоне не ясен. Например, когда в компоненте используется несколько миксинов, может быть сложно определить, из какого миксина происходит свойство, просто взглянув на шаблон. У HOC аналогичная проблема.
  • Конфликт пространства имен. Нет никакой гарантии, что примеси, разработанные разными разработчиками, не будут использовать одно и то же имя свойства или метода. У HOC аналогичная проблема с инъекционными пропсами.
  • представление. Как HOC, так и Renderless Components требуют дополнительной вложенности экземпляров компонентов для инкапсуляции логики, что приводит к ненужным потерям производительности.

Используя API на основе функций, мы можем извлечь связанный код в «композиционную функцию» — эта функция инкапсулирует связанную логику и предоставляет состояние компонента реактивному источнику данных.

import { reactive, computed, watch, onMounted } from 'vue'

const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = reactive(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}

Согласитель настроек дизайна мотивация

упомянутьConcentизsetupПеред мотивацией дизайна давайте рассмотрим официальныйhookдизайн мотивация

  • Повторное использование логики состояния между компонентами сложно
  • Сложные компоненты становятся трудными для понимания
  • непонятный класс

упоминается здесьМультиплексирование логического состояния жестко, заключается в том, что два фреймворка достигли точки консенсуса, и сообщество согласилось решить эту проблему путем различных попыток. В итоге все обнаружили интересное явление. Когда мы писали UI, мы принципиально не использовали наследование, а официальный Также настоятельно рекомендуется, чтобы идея комбинации была больше, чем наследование.Только представьте, кто бы написалBasicModal, а потом всякие***Modalунаследовано отBasicModalНаписать бизнес-реализацию? В основном основные дизайнеры компонентовBasicModalОставьте несколько портов и слотов, а потом вводитеBasicModalупакуйте это сами***ModalВсе кончено, да?

Таким образом, после того, как древовидная структура связанного списка на основе Fiber может имитировать стек вызовов функций,hookРождениеhookПросто сорвал размещение функциональных компонентовпорталЭтот портал очень волшебный, он может определять состояния, функции жизненного цикла и т.д., но между исходным хуком и дружеским опытом развития бизнеса все же есть некоторая пропасть, поэтому все начали делать на портале большую возню, с усердием и усердие. Искренне сосредоточьтесь на том, чтобы облегчить вам использование семейного ведра крючкаreact-use, также есть зацепки, ориентированные на определенное направление, например, ставшие популярными в последнее времяfetch dataОпытныйuseSWR, конечно же, есть и множество разработчиков, которые потихоньку насаживают свои бизнес-крючки Мешок.

но на основеhookОрганизационная бизнес-логика имеет следующие ограничения.

  • Функция временного закрытия должна быть повторно определена для каждого рендеринга.

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

  • Повторное использование хуков не является асинхронным и не подходит для организации сложной бизнес-логики.
function MyProjects () {
  const { data: user } = useSWR('/api/user')
  const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id)
  // When passing a function, SWR will use the
  // return value as `key`. If the function throws,
  // SWR will know that some dependencies are not
  // ready. In this case it is `user`.
  
  if (!projects) return 'loading...'
  return 'You have ' + projects.length + ' projects'
}

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

  • Процесс разработки хука и класса отличается, и между ними невозможно разделить логику.

Исходя из этих проблем,ConcentизsetupРодился, умное использование этого крючкапортал, позволить компоненту выполнить настройку при первом отображении компонента, тем самым открывая еще одно пространство, опосредуяfunction组件а такжеclass组件между ними, так что бизнес-логика двух может быть разделена друг с другом, таким образом достигаяfunction组件а такжеclass组件Идеальная ситуация гармоничного сосуществования, реализованнаяConcentосновные цели, будь тоfunction组件а такжеclass组件, все они просто носители пользовательского интерфейса, настоящая бизнес-логика вmodelвнутри.

Первый взгляд на useConcent

Главный герой этой статьиsetup, зачем упоминать здесьuseConcentШерстяная ткань? потому чтоsetupнужнопорталда, вConcentвнутриuseConcentИграя эту важную роль портала, мы шаг за шагом разберем код и, наконец, представимsetupчтобы сделать сравнение.

Чтобы узнать больше, вы можете просмотреть предыдущие статьи
Разговор об управлении состоянием и концепции дизайна Concent
илиВойдите в онлайн-среду IDE(Если щелчок по картинке недействителен, вы можете щелкнуть текстовую ссылку слева)

https://codesandbox.io/s/concent-guide-xvcej

определить модель

По соглашению используйте любойConcentОпределение модели должно быть настроено перед интерфейсом.

/**  ------ code in runConcent.js ------ */
import { run } from 'concent';
import { foo, bar, baz } from 'models';

run({foo, bar, baz});

/**  ------ code in models/foo/state.js ------ */
export default {
    loading: false,
    name: '',
    age: 12,
}

/**  ------ code in models/foo/reducer.js ------ */
export async function updateAge(payload, moduleState, actionCtx){
    const { data } = await api.serverCall();
    // 各种复杂业务逻辑略
    return {age: payload};
}

export async function updateName(payload, moduleState, actionCtx){
    const { data } = await api.serverCall();
    // 各种复杂业务逻辑略
    return {name: payload};
}

export async function updateAgeAndName({name, age}, moduleState, actionCtx){
    // actionCtx.setState({loading:true});

    // 任意组合调用其他reducer
    await actionCtx.dispatch(updateAge, age);
    await actionCtx.dispatch(updateName, name);
    // return {loading: false}; // 当前这个reducer本身也可以选择返回新的状态
}

Обратите внимание, что модель не обязательно настраивать централизованно во время прогона, ее также можно настроить с соседними компонентами.Стандартная структура организации кода показана на следующем рисунке.

использоватьconfigureНастроить модель страницы поблизости

Определите компонент концентрации

Ниже мы пройдемuseConcentОпределение функционального компонента Concent

function Foo(){
    useConcent();
    return (
        <div>hello</div>
    )
}

Это функциональный компонент Concent.Конечно, это определение бессмысленно, потому что ничего не делается, поэтому давайте добавим приватное состояние к этому функциональному компоненту.

function Foo(){
    // ctx是Concent为组件注的实例上下文对象
    const ctx = useConcent({state:{tip:'I am private', src:'D'}});
    const { state } = ctx;
    // ...
}

Хотя Concent гарантирует, что это состояние будет присвоено только ctx.state в качестве начального значения при первом рендеринге компонента, объект состояния будет временно создаваться каждый раз при повторном рендеринге компонента, поэтому лучше писать его снаружи функция.

const iState = {tip:'I am private', src:'D'}; //initialState

function Foo(){
    const ctx = useConcent({state:iState});
    const { state } = ctx;
    // ...
}

Если этот компонент будет создаваться одновременно, рекомендуется писать iState как функцию, чтобы обеспечить изоляцию состояния.

const iState = ()=> {tip:'I am private'}; //initialState

изменение состояния

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

function Foo(){
    const ctx = useConcent({state:iState});
    const { state, setState } = ctx;
    
    cosnt changeTip = (e)=> setState({tip:e.currentTarget.value});
    cosnt changeSrc = (e)=> setState({src:e.currentTarget.value});
    
    React.useEffect(()=>{
        console.log('首次渲染完毕触发');
        return ()=> console.log('组件卸载时触发');
    },[]);
    // ...
}

Разве это не выглядит немного странно, простоReact.setStateВызов дескриптора заменяется наuseConcentвернутьctxкоторый предоставилsetStatehandle, но если я хочу определить функцию побочного эффекта, которая срабатывает при изменении наконечника, тоReact.useEffectВо-вторых, как написать список параметров.Кажется, что вы можете напрямую передать в state.tip, но мы предлагаем лучший способ его записи.

настройка доступа

пришло время войтиsetupсейчас,setupСуть его в том, что он будет выполняться только один раз перед первоначальным рендерингом компонента, используяsetupОткройте новое пространство для завершения функциональной сборки компонентов!

мы определяем, когдаtipилиsrcБоковая функция, которая выполняет, когда она меняется

// Concent会将实例ctx透传给setup函数
const setup = ctx=>{
    ctx.effect(()=>{
        console.log('tip发生改变时执行');
        return ()=> console.log('组件卸载时触发');
    }, ['tip']);
    
    ctx.effect(()=>{
        console.log('tip和src任意一个发生改变时执行');
        return ()=> console.log('组件卸载时触发');
    }, ['tip', 'src'])
}

function Foo(){
    // useConcent里传入setup
    const ctx = useConcent({state:iState, setup});
    const { state, setState } = ctx;
    // ...
}

Заметьте нет!ctx.effectа такжеReact.useEffectИспользование точно такое же, за исключением того, что второй метод записи списка зависимостей параметров,React.useEffectВам нужно передать конкретное значение, иctx.effectНеобходимо передать имя stateKey, потому чтоConcentПредыдущее и старое состояние последнего состояния компонента всегда записываются.Сравнивая их, вы можете узнать, нужно ли запускать функцию побочного эффекта!

потому чтоctx.effectуже существует в другом пространстве, не подлежащемhookГрамматические правила ограничены, поэтому при желании можно даже так написать (конечно, для реального бизнеса не рекомендуется писать так, не зная правил)

const setup = ctx=>{
    ctx.watch('tip', (tipVal)=>{// 观察到tip值变化时,触发的回调
        if(tipVal === 'xxx' ){//当tip的值为'xxx'时,就定义一个新的副作用函数
            ctx.effect(()=>{
                return ()=> console.log('tip改变');
            }, ['tip']);
        }
    });
}

В приведенном выше примере мы завершили определение состояния и миграцию функции побочного эффекта, но изменение состояния все еще находится внутри функционального компонента, и теперь мы перемещаем их вsetupпространство, использованиеsetupВозвращенный объект можно найти вctx.settingsИспользуя эту функцию, метод записи обновляется до определения статического API, вместо того, чтобы его нужно было временно переопределять каждый раз, когда компонент повторно отрисовывается.

const setup = ctx=>{
    ctx.effect(()=>{ /** code */ }, ['tip']);
    
    cosnt changeTip = (e)=> setState({tip:e.currentTarget.value});
    cosnt changeSrc = (e)=> setState({src:e.currentTarget.value});
    return {changeTip, changeSrc};
}

function Foo(){
    const ctx = useConcent({state:iState, setup});
    const { state, setState, settings } = ctx;
    // 现在可以绑定settings.changeTip , settings.changeSrc 到具体的ui上了
}

Модель подключения

В приведенном выше примере компонент всегда оперирует своим собственным состоянием.Что делать, если вам нужно прочитать данные модели и оперировать методом модели? Вам нужно только отметить имя подключенного модуля.Обратите внимание, что состояние представляет собой комбинацию частного состояния и состояния модуля.Если в вашем частном состоянии есть ключ с тем же именем, что и состояние модуля, он будет автоматически использоваться модуль Значение состояния переопределяется.

function Foo(){
    // 连接到foo模块
    const ctx = useConcent({module:'foo', state:iState, setup});
    const { state, setState, settings } = ctx;
    // 此时state是私有状态和模块状态合成而来
    // {tip:'', src:'', loading:false, name:'', age:12}
}

Если вы ненавидите, когда состояние синтезируется и загрязняет вашеctx.state, вы также можете использоватьconnectпараметры для подключения модуля во времяconnectТакже позволяет подключать несколько модулей

function Foo(){
    // 通过connect连接到foo, bar, baz模块
    const ctx = useConcent({connect:['foo', 'bar', 'baz'], state:iState, setup});
    const { state, setState, settings, connectedState } = ctx;
    const { foo, bar, baz} = connectedState;
    // 通过ctx.connectedState读取到各个模块的状态
}

Бизнес-логика модулей мультиплексирования

Помните функцию редуктора модуля foo, которую мы определили выше? Теперь мы можем вызвать функцию редуктора напрямую через диспетчеризацию, так что мы можемsetupНа этом сборка этих функций моста завершена.

const setup = ctx=>{
    cosnt updateAgeAndName = e=> ctx.dispatch('updateAgeAndName', e.currentTarget.value);
    cosnt updateAge = e=> ctx.dispatch('updateAge', e.currentTarget.value);
    cosnt updateName = e=> ctx.dispatch('updateName', e.currentTarget.value);
    
    return {updateAgeAndName, updateAge, updateName};
}

Разумеется, указанное выше написание указывается при регистрации компонента Concent.moduleзначение, если используетсяconnectДля модулей, связанных по параметрам, нужно добавить четкий префикс модуля

const setup = ctx=>{
    // 调用的是foo模块updateAge方法
    cosnt updateAge = e=> ctx.dispatch('foo/updateAge', e.currentTarget.value);
}

так далее! Вы сказали, что ненавидите форму строковых вызовов, потому что видели в файле редьюсера модуля foo выше, что функции могут быть объединены напрямую на основе ссылок на функции.Очень неудобно писать имена здесь.ConcentУдовлетворить ваши потребности, основанные непосредственно на приложениях функции

import * as fooReducer from 'models/foo/reducer';
const setup = ctx=>{
    // dispatch fooReducer函数
    cosnt updateAge = e=> ctx.dispatch(fooReducer.updateAge, e.currentTarget.value);
}

Эм?什么,这样写也觉得不舒服,想直接调用,当然可以!

const setup = ctx=>{
    // 直接调用fooReducer
    cosnt updateAge = e=> ctx.reducer.foo.updateAge(e.currentTarget.value);
}

Делитесь бизнес-логикой с классом

Поскольку компоненты класса также поддерживают настройку и имеют объекты контекста экземпляра, вполне естественно совместно использовать бизнес-логику с функциональными компонентами.

import { register } from 'concent';

register('foo')
class FooClazzComp extends React.Component{
    ?setup(ctx){
        // 模拟componentDidMount
        ctx.effect(()=>{
            /** code */
            return ()=>{console.log('模拟componentWillUnmount');}
        }, []);
        ctx.effect(()=>{
            console.log('模拟componentDidUpdate');
        }, null, false);
        // 第二位参数depKeys写null表示每一轮都执行
        // 第三位参数immediate写false,表示首次渲染不执行
        // 两者一结合,即模拟出了componentDidUpdate
        
        cosnt updateAge = e=> ctx.dispatch('updateAge', e.currentTarget.value);
        return { updateAge }
    }
    
    render(){
        const { state, setState, settings } = this.ctx;
        // 这里其实this.state 和 this.ctx.state 指向的是同一个对象
    }
}

Мощный контекст экземпляра

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

Например, интерфейс эффекта, предоставляемый настройкой пользователю на ctx, нижний слой автоматически адаптирует функциональный компонент.useEffectи компоненты классаcomponentDidMount,componentDidUpdate,componentWillUnmount, тем самым сглаживая разницу в функциях жизненного цикла между функциональными компонентами и компонентами класса.

Например, интерфейс emit&on, предоставляемый на ctx, позволяет более слабо связывать компоненты через события, чтобы заставить целевой компонент выполнять некоторые другие действия в дополнение к режиму пользовательского интерфейса, управляемому данными.

Следующий рисунок полностью объясняет рабочие детали всего компонента Concent на этапах создания, существования и уничтожения.

Сравните настройку Vue3

Наконец, мы используем ConcentregisterHookCompинтерфейс для написания компонента иVue3 setupЧтобы сделать сравнение, я надеюсь, что этот шаг может произвести впечатление на вас как на разработчика React, Я считаю, что, основываясь на принципе неизменности, вы также можете писать элегантно скомпонованные функциональные компоненты типа API.

registerHookCompв основном на основеuseConcentПоверхностная инкапсуляция, которая автоматически оборачивает возвращаемый компонент функции слоем.React.memo ^_^

import { registerHookComp } from "concent";

const state = {
  visible: false,
  activeKeys: [],
  name: '',
};

const setup = ctx => {
  ctx.on("openMenu", (eventParam) => { /** code here */ });
  ctx.computed("visible", (newState, oldState) => { /** code here */ });
  ctx.watch("visible", (newState, oldState) => { /** code here */ });
  ctx.effect( () => { /** code here */ }, []);
  
  const doFoo = param =>  ctx.dispatch('doFoo', param);
  const doBar = param =>  ctx.dispatch('doBar', param);
  const syncName = ctx.sync('name');
  
  return { doFoo, doBar, syncName };
};

const render = ctx => {
  const {state, settings} = ctx;

  return (
    <div className="ccMenu">
      <input value={state.name} onChange={settings.syncName} />
      <button onClick={settings.doFoo}>doFoo</button>
      <button onClick={settings.doBar}>doBar</button>
    </div>
  );
};

export default registerHookComp({
  state, 
  setup,  
  module:'foo',
  render
});

Эпилог

❤ отметьте меня, если вам нравится концепт ^_^, Разработка Concent неотделима от вашей духовной поддержки и ободрения, и я рассчитываю на ваше понимание и обратную связь. Давайте вместе создавать более интересные, надежные и высокопроизводительные реагирующие приложения.

Следующее уведомление [concent love typescript], потому что весь API Concent функционально-ориентирован, а комбинация с ts — это естественная пара хороших друзей, поэтому писать concent на основе ts будет очень просто и удобно 😀, пожалуйста, ждите к этому .

Настоятельно рекомендуется ввести модификацию кода онлайн-форка IDE, если вы заинтересованы (если щелчок по картинке недействителен, вы можете нажать на текстовую ссылку)

Edit on CodeSandbox

https://codesandbox.io/s/concent-guide-xvcej

Edit on StackBlitz

https://stackblitz.com/edit/cc-multi-ways-to-wirte-code

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