❤ отметьте меня, если вам нравится концепт ^_^
Управляемое чтение
Закончил статью в прошлом номереСоздание компонента и обновление статуса операции 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(Если щелчок по картинке недействителен, вы можете щелкнуть текстовую ссылку слева)
определить модель
По соглашению используйте любой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
который предоставилsetState
handle, но если я хочу определить функцию побочного эффекта, которая срабатывает при изменении наконечника, то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, если вы заинтересованы (если щелчок по картинке недействителен, вы можете нажать на текстовую ссылку)
Если у вас есть какие-либо вопросы по поводу содержания, вы можете отсканировать код и присоединиться к групповой консультации, я постараюсь ответить на ваши вопросы и помочь вам узнать больше.