❤ отметьте меня, если вам нравится концепт ^_^
Управление кодом побочных эффектов
Когда хуки еще не родились, у нас обычно есть встроенные функции жизненного цикла в классах.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入侵、渐进式、高性能的增强型状态管理方案
.
Для меня Хуафа уже начал падать, но если я смогу обменять больше развития ценой падения одного человека, я смогу сохранить эти черные и красивые густые волосы, я чувствую, что это того стоит в одно мгновение, ха-ха 😀