Автор: Ю Че
задний план
Друзья, которые обращали внимание на разработку мини-программ, должны были заметить, что в самом начале мини-программы создавались как основа для микро-инновационного бизнеса и могут запускать не более 1 млн пакетов. Однако позже выяснилось, что многие производители переводят все больше и больше предприятий на мини-программы, а возможности мини-программ постоянно открываются и становятся все более и более мощными. Так что позже лимит пакетов был увеличен до 2м, а затем были введены подпакеты, и теперь можно загружать 8м апплет. На самом деле этого объема уже можно достичь очень большим и очень сложным бизнесом. С точки зрения пользователей Taro, например, апплет для покупок Jingdong и апплет 58.com не уступают бизнесу на стороне ПК с точки зрения количества кодов и сложности, поэтому мы можем сказать, что сложность фронт- окончательная разработка смещается в сторону апплета.
И разработка апплета на самом деле является подмножеством фронтенд-разработки Во всей фронтенд-индустрии, как мы решаем проблему сложности?
Сначала мы рассмотрим React: член React Core Team и автор Redux Дэн Абрамов впервые представил React Hooks на ReactConf 2018. React Hooks были введены для решения некоторых проблем с компонентом класса:
- Логику между компонентами класса сложно использовать повторно. Поскольку JavaScript не похож на Go или C++, класс может наследовать несколько раз, и повторное использование логики класса становится проблемой;
- Сложные компоненты трудно понять. Компонент класса часто выполняет некоторые побочные функции мониторинга событий сбора данных в жизненном цикле.В этом случае нам трудно разделить компонент на более мелкие сильные стороны;
- Классы сбивают с толку. Многие новички должны быть привязаны к событиям с помощью компонентов класса.
this
Запутавшись, вы можете использовать привязку для привязки событий, вы можете напрямую писать стрелочные функции или вы можете писать функции атрибутов класса, но какой метод лучше? В ES 2018 класс имеет множество синтаксисов, таких как декораторы, такие как частные поля, Эти странные синтаксисы также добавляют новичкам путаницы.
Та же проблема существует и для Vue.Автор Vue, г-н Юси Ю, также представил новую функцию Vue 3.0 на VueConf China 2019 под названиемFunctional-based APIКонцепция нового API, вдохновленная React Hooks. Поскольку шаблон композиции компонентов Vue 2.0 представлен в виде литералов объектов, поэтомуFunctional-based APIЕго можно использовать в качестве альтернативы Mixins с новым адаптивным API в качестве нового режима композиции компонентов. Итак, мы все еще очень мало знаем о Vue 3.0, и API может измениться в будущем, но, возможно, герои видят то же самое.React и Vue оба выбирают хуки для упрощения разработки интерфейса.Почему?
Мы можем посмотреть на предыдущую схему комбинации компонентов, перваяMixins, обведено краснымMixins, желтый компонент, мы знаемMixinsПо сути, это объединение нескольких объектов в один объект.MixinsПроцесс немного похож на вызовObject.assgin
метод. ЭтоMixinsВ чем проблема? Во-первых, это связывание пространства имен.Если несколько объектов имеют параметры с одинаковыми именами, эти параметры будут связаны друг с другом;MixinsЭто должно быть время выполнения, чтобы знать, какие параметры есть, поэтому TypeScript не может выполнять статическую проверку; в-третьих, параметры компонента не ясны.MixinsПропсы компонента ничем не отличаются от других параметров и могут быть легко изменены.Mixinsперезаписать.
чтобы решитьMixinsпроблема, а позже разработали компоненты более высокого порядка (HOC) компоненты более высокого порядка такие же, как на рисунке, один компонент вложен в другие компоненты. это решаетMixinsНекоторые проблемы, такие как развязка пространства имен, потому что каждый раз генерируются новые компоненты, проблемы с пространством имен нет; во-вторых, он также может очень хорошо выполнять статическую проверку; но он все еще не может справиться с проблемой реквизитов компонентов, реквизиты все еще Он может быть изменен в компонентах более высокого порядка, и у него есть новые проблемы, каждый раз, когда используется компонент более высокого порядка, будет дополнительный экземпляр компонента.
Наконец, давайте взглянем на крючки. Фиолетовый кружок — это крючки. Как и на картинке, все крючки находятся в одном компоненте, и крючки также могут вызывать друг друга. Поскольку хуки запускаются в обычном функциональном компоненте, проблем с пространством имен быть не должно.В то же время TypeScript также может выполнять хорошие статические проверки обычных функций, а хуки не могут изменять свойства компонента.Что передается в конце Что доступен; в конце концов, хуки не будут генерировать новые компоненты, поэтому он является однокомпонентным экземпляром.
В Taro 1.3 мы реализовали кучу фич, изюминкой которых стала поддержка React Hooks. Хотя React Hooks долгое время официально не был стабильным, мы считаем, что эта функция может эффективно упростить режим разработки, повысить эффективность разработки и опыт разработки. Даже если экология и передовой опыт хуков еще не совершенны, мы считаем, что хуки станут основным направлением модели разработки React в будущем и окажут глубокое влияние на будущий состав API других фреймворков. Так что в планах Таро мы также поставили Крюков на очень важное место.
Что такое крючки?
Я считаю, что автор так много наговорил, и у каждого должен быть какой-то интерес к хукам.Так что же такое хуки? Короче говоря, хуки — это набор функций, которые запускаются внутри компонентов React, что позволяет вам использовать состояние и другие функции без написания классов. В частности, хуки могут принимать форму:
useState
с внутренним состоянием
Мы можем посмотреть на простой случай родного апплета, простой компонент счетчика, нажать кнопку + 1, я считаю, что каждый друг фронтенд-разработки может легко написать компонент счетчика. Но мы можем немного изменить код и изменить обработчик событий на стрелочную функцию. Если это так, код не будет работать. На самом деле в нативной разработкеthis
Проблема постоянна, поэтому нам часто приходится открывать новую переменную, чтобы поместитьthis
кешируется, называетсяself
что-то, чтобы избежать подобных проблем. Мы также упоминали ранее, что если вы используете класс ES6 для организации компонентов, вы также столкнетесь сthis
Укажите на неясные вопросы.
Page({
data: {
count: 0
},
increment: () => { // 这里写箭头函数就跑不了了
this.setData({
count: this.data.count + 1
})
}
})
Давайте взглянем на наш метод написания хуков. Мы ввели функцию под названием useState, которая принимает параметр начального значения и возвращает кортеж. Если вы пишете бэкенд-студентов, вы должны быть знакомы с этим режимом. Так же, как Koa или Go , a Функция возвращает два значения или кортеж, но первый параметр, который мы возвращаем, — это текущее состояние, а другой — функция, которая устанавливает это состояние.setState
Функции приведут к отображению всего компонента. Затем они деконструировали их с синтаксисом структуры ES6.
Затем мы определяем добавленную функцию, привязывая ее кonClick
на мероприятии.
function Counter () {
// 返回一个值和一个设置值的函数
// 每次设置值之后会重新渲染组件
const [ count, setCount ] = useState(0)
function increment () {
setCount(count + 1)
}
return (
<View>
<Text>You clicked {count} times</Text>
<Button onClick={increment}>
Click me
</Button>
</View>
)
}
Также очень простой код. Если вы знакомы с предыдущей версией Taro, вы знаете, что такой код не может работать в предыдущем Taro, но после Taro 1.3 параметр события может передавать любое допустимое значение, если вы хотите написать стрелочную функцию напрямую или написать Каррирование Функция также полностью без проблем.
Вы можете обнаружить, что хуки, которые мы используем, представляют собой очень простую и обычную функцию.this
нет класса, нет конструктора класса, не болееthis
, больше никогдаthis
,self
Тупо непонятная ситуация.
Вы можете запомнить этот простой компонент счетчика, и многие из случаев, описанных ниже, основаны на этом компоненте.
useEffect
с побочными эффектами
Далее давайте рассмотрим немного более сложный пример, компонент обратного отсчета.Мы нажимаем кнопку, чтобы начать обратный отсчет, а затем нажимаем, чтобы остановить обратный отсчет. В нашем компоненте у нас есть две переменные,start
Используется для управления запуском отсчета времени,time
Это наше время обратного отсчета. Обратите внимание, что нам нужно очистить несколько разinterval
, а в развитии реального бизнеса этоtouchStart
Функции могут быть намного более сложными и понятными на раннем этапе, если вы не будете осторожныinterval
Или забыть очистить.
Page({
data: {
time: 60
},
start: false,
toggleStart () {
this.start = !this.start
if (this.start) {
this.interval = setInterval(() => {
this.setData({
time: this.data.time - 1
})
}, 1000)
} else {
clearInterval(this.interval)
}
},
onUnload () {
clearInterval(this.interval)
}
})
<view>
<button bindtap="toggleStart">
{{time}}
</button>
</view>
И наш пример с хуками будет выглядеть так: мы вводимuseEffect
функция. Как мы упоминали ранее, каждый раз, когда мы вызываемuseState
вернутьsetState
Функция повторно вызовет всю функцию, которая на самом деле включаетuseEffect
функция,useEffect
Принимает два параметра. Во-первых, это побочный эффект, т.effect
функция, которая не принимает и не возвращает никаких параметров.
Второй параметр — это зависимый массив, который вызывается при изменении переменных в массиве, первый параметрeffect
функция.Effect
Функция также может возвращать функцию, которая выполняется на следующемeffect
Когда функция вызывается или каждый раз, когда компонент выходит из системы или когда он будет вызываться, мы можем здесь очистить некоторые подписки на события или поведение интервалов, которые могут вызвать утечку памяти.
В нашем примере, когдаstart
Повторный запуск для каждого измененияeffect
функция, которая будет устанавливаться каждую секундуtime
Его значение уменьшает его на единицу, но такой способ написания проблематичен.
function Counter () {
const [ start, setStart ] = useState(false)
const [ time, setTime ] = useState(60)
useEffect(() => { // effect 函数,不接受也不返回任何参数
let interval
if (start) {
interval = setInterval(() => {
// setTime(time - 1) ❌ time 在 effect 闭包函数里是拿不到准确值的
setTime(t => t -1) // ✅ 在 setTime 的回调函数参数里可以拿到对应 state 的最新值
}, 1000)
}
return () => clearInterval(interval) // clean-up 函数,当前组件被注销时调用
}, [ start ]) // 依赖数组,当数组中变量变化时会调用 effect 函数
return (
<View>
<Button onClick={() => setStart(!start)}>{time}</Button>
</View>
)
}
потому что мыsetInterval
При замыкании этой функции мы захватываемtime
Значение этой переменной не может соответствовать последнему значению,time
Возможно, что значение изменяется несколько раз неожиданно. Решение также очень простое, как мы упоминали ранее.useState
вернутьsetState
метод, который может принимать функцию в качестве параметра, а параметр этой функцииstate
Последнее значение, так что пока мы передаем функцию, все в порядке. Это один из способов.
Еще один способ - использоватьuseRef
крючки,useRef
Вы можете вернуть изменяемую ссылку, она сгенерирует объект, в объекте это имеетcurrent
свойства, при этомcurrent
Значение является изменяемым. В нашем примере каждое изменениеcurrentTime.current
все синхронно иcurrentTime
является ссылкой, поэтомуcurrentTime.current
Должен быть управляемым.
function Counter () {
const [ start, setStart ] = useState(false)
const [ time, setTime ] = useState(60)
const currentTime = useRef(time) // 生成一个可变引用
useEffect(() => { // effect 函数,不接受也不返回任何参数
let interval
if (start) {
interval = setInterval(() => {
setTime(currentTime.current--) // currentTime.current 是可变的
}, 1000)
}
return () => clearInterval(interval) // clean-up 函数,当前组件被注销时调用
}, [ start ]) // 依赖数组,当数组中变量变化时会调用 effect 函数
return (
<View>
<Button onClick={() => setStart(!start)}>{time}</Button>
</View>
)
}
Хотя мы можемuseRef
решить эту проблему, но это не обязательно. потому чтоsetTime
Метод передачи callback-функции явно более читабелен. Что действительно необходимо, так это поставить нашinterval
Переменная действует как ссылка, и мы помещаем ее в область видимости функции верхнего уровня.interval
в качестве ссылки, чтобы мы могли очистить его в любом месте этой функции, тогда как в исходном коде мы поместилиinterval
Поместите его в функцию эффекта как обычную переменную, поэтому, если у нас есть событие, которое также требует очистки интервала, это невозможно сделать. но сuseRef
Генерация изменяемых ссылок не имеет этого ограничения.
function Counter () {
const [ start, setStart ] = useState(false)
const [ time, setTime ] = useState(60)
const interval = useRef() // interval 可以在这个作用域里任何地方清除和设置
useEffect(() => { // effect 函数,不接受也不返回任何参数
if (start) {
interval.current = setInterval(() => {
setTime(t => t - 1) // ✅ 在 setTime 的回调函数参数里可以拿到对应 state 的最新值
}, 1000)
}
return () => clearInterval(interval.current) // clean-up 函数,当前组件被注销时调用
}, [ start ]) // 依赖数组,当数组中变量变化时会调用 effect 函数
return (
<View>
<Button onClick={() => setStart(!start)}>{time}</Button>
</View>
)
}
useContext
Взаимодействие с кросс-компонентами
Далее рассмотрим пример межкомпонентной связи, например, у нас есть три компонента,pageкомпонент имеетchildкомпоненты,childкомпонент имеетcounterкомпоненты, и мыcounterкомпонентcount
ценность иsetCount
функция, этоpageкомпонент передан вниз. Такая ситуация часто встречается при разработке сложного бизнеса, что делать при разработке нативных апплетов?
Нам нужно вручную поставить нашcounter
Значения и функции передаются вручную последовательно, и такая передача должна быть явной.Необходимо задать параметры реквизита в JavaScript, а также необходимо задать параметры реквизита в WXML.Можно не менее одного , Не двигайтесь. Мы также заметили, что даже если у дочернего компонента нет никакой бизнес-логики, он должен установитьtriggerEvent
Объявления типов для функций и свойств. Этот способ написания, несомненно, очень громоздкий и ограничительный.
<!-- page.wxml -->
<view>
<child />
</view>
<!-- child.wxml -->
<view>
<counter />
</view>
<!-- counter.wxml -->
<view>
<text>
You clicked {{count}} times
</text>
<butto bindtap="increment">
Click me
</button>
</view>
// page.js
Page({
data: {
count: 0
},
increment () {
this.setData({
count: this.data.count + 1
})
}
})
// child.js
Component({
properties: {
count: Number
},
methods: {
increment () {
this.triggerEvent('increment')
}
}
})
// counter.js
Component({
properties: {
count: Number
},
methods: {
increment () {
this.triggerEvent('increment')
}
}
})
И мы можем посмотреть, как пишутся хуки, сначала мы используемTaro.createContext
Создаватьcontext
объект, в нашемpageкомпонент положить нашcount
а такжеsetCount
Функция передается как объектContext.Provider
изvalue
внутри. тогда в нашемCounterкомпонент, мы можем использоватьuseContext
Эти крючки берут нашиcount
а такжеsetCount
Выньте его, и вы можете использовать его напрямую.
export const CounterContext = Taro.createContext(null);
// page.js
const Page = () => {
const [ count, setCount ] = useState(0)
return (
<CounterContext.Provider value={{ count, setCount }}>
<Child />
</CounterContext.Provider>
);
};
// child.js
const Child = () => (
<View>
<Counter />
</View>
);
// counter.js
const Counter = () => {
const { count, setCount } = useContext(CounterContext)
return (
<View>
<Text>
You clicked {count} times
</Text>
<Button onClick={() => setCount(count + 1)}>
Click me
</Button>
</View>
)
}
Вы можете обнаружить, что код, использующий Context, намного проще исходного кода, и параметры не нужно явно передавать уровень за уровнем.childКомпоненты тоже просто как правда, без единой строчки лишней логики. Но оптимизация — не самое большое преимущество. Самым большим преимуществом является то, что вы можете обнаружить, что наш контекст может передавать сложный объект. Студенты, знакомые с собственной разработкой мини-программ, могут знать, что передача всех реквизитов будет сериализована мини-программой. Если передается сложный объект , он в конечном итоге изменится на JSON. Но в контексте Таро такого ограничения нет, вы можете передать объект с помощью функции или передать что-то вродеimutabale
илиobserable
такой сложный объект. В таро 1.3 мы переработали систему реквизита.Контекст таро тот же, что и у реквизита.Нет ограничений на передачу свойств.Вы можете передавать все что хотите.
Еще один момент, на который стоит обратить внимание, это то, что передача контекста может игнорировать стратегию обновления родительского компонента.В этом примере, даже если мы передаемshouldComponentUpdate()
запрещенchildобновление компонентов, ноcounterПоскольку его дочерние компоненты все еще могут быть обновлены. Эта функция позволяет нам быть более гибкими при оптимизации производительности.
Хуки на самом деле дерутся в мини-программах
Поговорив об основном использовании хуков, некоторые ученики подумают: «Эй, почему мне кажется, что эти вещи обычные, ничего особенного». Но на самом деле эти базовые Крючки не могут сыграть злую шутку по отдельности, но в сочетании они могут превратиться в мощную силу.
Пользовательские крючки
Каждый может столкнуться с такой потребностью в бизнес-разработке реализовать событие двойного клика.Если вы разрабатывали из H5, то можете написать его напрямую.onDoubleClick
, но, к сожалению, компонент апплета неdoubleClick
этого события. Конечно, если вы используете Taro и TypeScript, такой ошибки вы не допустите, редактор сразу выдаст вам ошибкуText
Компоненты не имеют этого свойства.
Так вы сами реализуете событие двойного клика.Код наверное такой.В качестве состояния есть время последнего клика.Каждый раз при срабатывании одномашинного события оно сравнивается со временем последнего клика. Если интервал слишком мал, то он является событием двойного щелчка. Код очень простой, но у нас не может не возникнуть проблема: каждый раз, когда мы добавляем событие клика в компонент, мы каждый раз добавляем такую кучу кода?
function EditableText ({ title }) {
const [ lastClickTime, setClickTime ] = useState(0)
const [ editing, setEditing ] = useState(false)
return (
<View>
{
editing
? <TextInput editing={editing} />
: <Text
onClick={e => {
const currentTime = e.timeStamp
const gap = currentTime - lastClickTime
if (gap > 0 && gap < 300) { // double click
setEditing(true)
}
setClickTime(currentTime)
}}
>
{title}
</Text>
}
</View>
)
}
В настоящее время мы можем написать собственные хуки, код аналогичен исходному коду,useDoubleClick
не принимает аргументов, но когда мы вызываемuseDoubleClick
возвращает файл с именемtextOnDoubleClick
функции, в параметре события компонента Text мы затемtextOnDoubleClick
Функция обратного вызова передается в функцию, и эта функция обратного вызова является функцией, когда срабатывает условие двойного щелчка. Когда мы каррируем этот пользовательский хук, мы можем предоставить функцию обратного вызова только тогда, когда используется хук:
function useDoubleClick () {
const [ lastClickTime, setClickTime ] = useState(0)
return (callback) => (e) => {
const currentTime = e.timeStamp
const gap = currentTime - lastClickTime
if (gap > 0 && gap < 300) {
callback && callback(e)
}
setClickTime(currentTime)
}
}
function EditableText ({ title }) {
const [ editing, setEditing ] = useState(false)
const textOnDoubleClick = useDoubleClick()
return (
<View>
{
editing
? <TextInput editing={editing} />
: <Text
onClick={textOnDoubleClick(() =>
setEditing(true)
)}
>
{title}
</Text>
}
</View>
)
}
Функция каррирования кажется немного окольной, но как только мы это сделали, наши пользовательские хуки можно вызывать несколько раз, например:
function EditableText ({ title }) {
const textOnDoubleClick = useDoubleClick()
const buttonOnDoubleClick = useDoubleClick()
// 任何实现单击类型的组件都有自己独立的双击状态
return (
<View>
<Text onClick={textOnDoubleClick(...)}>
{title}
</Text>
<Button onClick={buttonOnDoubleClick(...)} />
</View>
)
}
Каждый может представить себе, что если мы реализуем его в соответствии с нашими традиционными реквизитами рендеринга, нам придется каждый раз писать еще один компонент-контейнер.Если мы используем для его реализации миксины или компоненты более высокого порядка, это будет более хлопотно. создайте новый на основе каждого другого типа компонента. С помощью хуков любой компонент, который реализует автономный тип, может добиться эффекта двойного щелчка с помощью нашего пользовательского хука, который очень элегантен как с точки зрения его внутренней реализации, так и с помощью предоставляемого им API.
оптимизация производительности
Далее поговорим об оптимизации производительности, я думаю, что у всех была такая ситуация, есть массив, и ему нужно только один раз заставить свой пропс отрендериться, и после этого ему никогда не нужно будет его обновлять. Для традиционного компонента класса мы можем установитьshouldComponentUpdate()
вернутьfalse
.
class Numbers extends Component {
shouldComponentUpdate () {
return false
}
render () {
return <View>
{
expensive(this.props.array).map(i => <View>{i}</View>)
}
</View>
}
}
И с функциональными компонентами мы можем сделать то же самое. Таро предоставляет то же, что и ReactTaro.memo
API, его первый параметр принимает функциональную составляющую, второй параметр и нашshouldComponentUpdate()
Таким же образом определите, при каких обстоятельствах компонент нуждается в обновлении. Если второй параметр не передан,Taro.memo
Эффект такой же, какTaro.PureComponent
Таким же образом выполните поверхностное сравнение старого и нового реквизита и обновите компонент, если поверхностное сравнение не равно.
function Numbers ({ array }) {
return (
<View>
{
expensive(array).map(
i => <View>{i}</View>
)
}
</View>
)
}
export default Taro.memo(Numbers, () => true)
Во втором случае мы можем обратиться к нашему старому другу, компоненту счетчика. Но этот компонент-счетчик отличается от старого друга двумя моментами: во-первых, каждый раз, когда вы нажимаете +1, нужно вызывать счетчикexpensive
Функция выполняет цикл 100 миллионов раз, чтобы получить нужное нам значение, и второй момент заключается в том, что у нее есть еще одно значение.Input
компоненты. В нашей реальной разработке бизнеса эта ситуация также очень распространена: нашему компоненту может потребоваться выполнить дорогостоящую обработку данных, чтобы получить конечное желаемое значение, но этот компонент также имеет несколько состояний, которые управляют другими компонентами. В этом случае, если мы нормально напишем бизнес-логику, будут проблемы с производительностью:
function Counter () {
const [ count, setCount ] = useState(0)
const [val, setValue] = useState('')
function expensive() {
let sum = 0
for (let i = 0; i < count * 1e9; i++) {
sum += i
}
return sum
}
return (
<View>
<Text>You clicked {expensive()} times}</Text>
<Button onClick={() => setCount(count + 1)}>
Click me
</Button>
<Input value={val} onChange={event => setValue(event.detail.value)} />
</View>
)
}
из-за СШАcount
За значением следуетInput
значение не имеет значения, но каждый раз, когда мы меняемInput
значение, это вызовет повторную визуализацию компонента. То есть этот цикл повторяется 100 миллионов раз.expensive()
Функция будет вызвана снова. Такая ситуация явно неприемлема. Для решения этой проблемы мы можем использоватьuseMemo
API.useMemo
подпись иuseEffect
Вроде как, разница в том, чтоuseMemo
Первая функция имеет возвращаемое значение, и значение, возвращаемое этой функцией, такжеuseMemo
Возвращаемое значение функции. Второй параметр тоже зависит от массива, только при изменении данных этого массива,useMemo
Функция будет пересчитана, если массив не изменился, то данные будут взяты прямо из кеша. В нашем случае нам просто нужноcount
Изменение рассчитывается, и нет необходимости рассчитывать изменение входного значения.
function Counter () {
const [ count, setCount ] = useState(0)
const [val, setValue] = useState('')
const expensive = useMemo(() => {
let sum = 0
for (let i = 0; i < count * 100; i++) {
sum += i
}
return sum
}, [ count ]) // ✅ 只有 count 变化时,回调函数才会执行
return (
<View>
<Text>You Clicked {expensive} times</Text>
<Button onClick={() => setCount(count + 1)}>
Click me
</Button>
<Input value={val} onChange={event => setValue(event.detail.value)} />
</View>
)
}
API двух меморандумов, которые мы только что упомянули, его полное имя на самом делеMemoization. Поскольку хуки запускаются в обычных функциях, мы должны эффективно использовать методы кэширования и запоминания для оптимизации производительности.
В компьютерных науках мемоизация — это метод оптимизации, который увеличивает скорость работы программы. Он экономит время вычислений, сохраняя возвращаемое значение дорогостоящей в вычислительном отношении функции и извлекая результат из кеша, когда он снова понадобится, без необходимости его повторного вычисления.
Масштабное государственное управление
Когда дело доходит до управления состоянием, самым известным инструментом в сообществе React, конечно же, является Redux. существуетreact-redux@7
Zhongxin ссылается на три API:
-
useSelector
. это немного похожеconnect()
первый параметр функцииmapStateToProps
, вывести данные из состояния; -
useStore
. вернутьstore
сам; -
useDispatch
. вернутьstore.dispatch
.
В Таро вы действительно можете использоватьcreateContext
а такжеuseContext
напрямуюuseStore
а такжеuseDispatch
Достигнуто. и на основеuseStore
а такжеuseDispatch
так же какuseState
,useMemo
,useEffect
также может быть достигнутоuseSelector
. то естьreact-redux@7
Все новые API представляют собой пользовательские хуки, созданные из обычных хуков. Конечно, мы такжеreact-redux@7
новые функции перенесены на@tarojs/redux
, вы можете использовать эти API непосредственно в версии Taro 1.3.
Реализация хуков
Теперь у нас есть следующее понимание хуков, юридические хуки должны соответствовать следующим требованиям для выполнения:
- Может вызываться только из функциональной функции
- Может вызываться только на верхнем уровне функции
- нельзя вызывать в условном выражении
- нельзя вызывать в цикле
- нельзя вызывать во вложенных функциях
Я хотел бы попросить вас подумать, почему функция Hook должна соответствовать вышеуказанным требованиям? Я хотел бы попросить всех подумать об этой проблеме с точки зрения разработчиков фреймворка, а не оглядываться назад с точки зрения тех, кто вызывает API. Когда вызывается функция-ловушка, внутренняя реализация функции-ловушки должна иметь доступ к выполняющемуся в данный момент компоненту, но входной параметр нашего API-интерфейса не передается в этот компонент, поэтому какова конструкция, позволяющая разрешить? функция получает доступ к исполняющему компоненту и точно определяет свое местонахождение?
Умные друзья могли догадаться, что все эти подсказки указывают на результат,Хуки должны быть функцией, которая выполняется по порядку.. Другими словами, независимо от того, сколько раз весь компонент будет выполняться и отображаться, порядок хуков в компоненте не изменится.
Мы также знаем еще одно правило: хуки — это функции внутри функций React, поэтому мы можем знать, что наиболее важными проблемами для реализации хуков являются две:
- Найдите исполняемую функцию React
- Найдите порядок, в котором выполняются хуки.
Мы можем установить глобальный объект с именемCurrentOwner
, у него есть два свойства, первоеcurrent
, он выполняетTaro
функцию, мы можем установить ее значение, когда компонент загружается и обновляется, а затем установить его вnull
; второе свойствоindex
, этоCurrentOwner.current
Порядок хуков в хуке увеличивается на 1 каждый раз, когда мы выполняем функцию хука.
const CurrentOwner: {
current: null | Component<any, any>,
index: number
} = {
// 正在执行的 Taro 函数,
// 在组件加载和重新渲染前设置它的值
current: null,
// Taro 函数中 hooks 的顺序
// 每执行一个 Hook 自增
index: 0
}
На самом деле такой объект в React есть, и вы тоже можете его использовать, он называется__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner
, что означает, что если вы хотите реализовать хуки для React 15, вы действительно можете это сделать. Но, как и в случае с его названием, если вы его используете, вас могут уволить и оптимизировать, поэтому лучшим решением будет использовать наше таро напрямую.
Далее реализуем нашуgetHook
функция, которая также очень проста, еслиCurrenOwner.current
даnull
, то это не легальная функция хука, мы напрямую сообщаем об ошибке. Если условие выполнено, ставим крючокindex
+ 1, то сохраняем Хуки компонента в массив, еслиindex
Если она больше длины хуков, значит, хуки не были созданы, поэтому мы пушим пустой объект, чтобы избежать ошибок времени выполнения при взятии значений позже. Затем возвращаемся непосредственно к нашему Крюку.
function getHook (): Hook {
if (CurrentOwner.current === null) {
throw new Error(`invalid hooks call: hooks can only be called in a taro component.`)
}
const index = CurrentOwner.index++ // hook 在该 Taro 函数中的 ID
const hooks: Hook[] = CurrentOwner.current.hooks // 所有的 hooks
if (index >= hooks.length) { // 如果 hook 还没有创建
hooks.push({} as Hook) // 对象就是 hook 的内部状态
}
return hooks[index] // 返回正在执行的 hook 状态
}
Теперь, когда мы нашли хуки, которые мы реализуем, нетрудно реализовать хуки целиком. мы обсуждали раньшеuseState
, теперь рассмотрим его реализацию пошагово.
Сначала, еслиinitState
является функцией, выполните ее напрямую. Во-вторых, позвоните нам, что мы написали ранееgetHook
функция, которая возвращает состояние хука. Далее основная логика useState.Если у хука нет состояния, то мы сначала кэшируем исполняемый компонент, а потомuseState
Что возвращено, то нашеhook.state
, на самом деле является массивом, первое значение, конечно же, мыinitState
, первый параметр это функция, если это функция, то мы ее выполним, иначе мы напрямую присвоим параметр нашемуhook.state
Первое значение, после завершения присваивания добавляем текущий компонент в очередь обновлений и ждем обновления.
function useState<S> (initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] {
if (isFunction(initialState)) { // 如果 initialState 是函数
initialState = initialState() // 就直接执行
}
const hook = getHook() as HookState<S> // 找到该函数中对应的 hook
if (isUndefined(hook.state)) { // 如果 hook 还没有状态
hook.component = Current.current! // 正在执行的 Taro 函数,缓存起来
hook.state = [ // hook.state 就是我们要返回的元组
initialState,
(action) => {
hook.state[0] = isFunction(action) ? action(hook.state[0]) : action
enqueueRender(hook.component) // 加入更新队列
}
]
}
return hook.state // 已经创建 hook 就直接返回
}
Наконец мы положилиhook.state
Вернитесь назад, и все готово.
Крючки Таро имеют в общей сложности восемь API,useState
Реализация хуков может показаться очень простой, но на самом деле ее объем кода и сложность являются вторыми по величине среди всех реализаций хуков. Так что на самом деле никаких черных технологий у Hooks нет, и каждый может с уверенностью пользоваться ими.
Резюме и перспективы
В 2018 году автор Ember.js отметил,Compilers are the New Frameworks, компилятор — это фреймворк. Что это значит? Возьмите React в качестве примера, один React на самом деле бесполезен, вам также нужно сотрудничатьcreate-react-app, eslint-plugin-react-hooks, prettierТаким образом, инструменты, связанные с компиляцией, в конечном итоге сформируют основу, и эти инструменты также созданы людьми из основной команды React. И эта тенденция характерна не только для React, вы можете обнаружить, что в 2018 году основной задачей Юси Ю является разработкаvue-cli. в то время как для некоторых более агрессивных фреймворков, таких какsvelte, его фреймворк — это компилятор, а компилятор — это фреймворк.
А в 2019 году я хочу предложить новую концепцию под названием «фреймворк экология». Возьмем в качестве примера Taro.С помощью Taro можно повторно использовать вещи экосистемы React.При этом у Taro также естьtaro doctor
, сообщество разработчиков Taro, рынок материалов Taro, разработка облачных апплетов Tencent и другие партнеры вместе составляют экосистему Taro, а вся экосистема Taro является основой. В течение последних шести месяцев мы продолжали улучшать и оптимизировать производительность фреймворка Taro.Все упомянутые выше возможности и функции могут нормально использоваться в Taro 1.3. В дополнение к фреймворку мы также глубоко взрастили сообщество, запустили рынок материалов Taro и сообщество разработчиков Taro, а также провели конкурс по разработке материалов в сотрудничестве с Tencent Mini Program Cloud Development. Теперь мы искренне приглашаем вас присоединиться к сообществу, чтобы внести свой вклад в разработку мини-программы лучше, быстрее и удобнее:
- Официальный сайт Таро:taro.jd.com/
- Рынок материалов Таро:taro-ext.jd.com/
- Сообщество разработчиков Таро:taro-club.jd.com/
Официальный рынок материалов приглашает вас принять участие в конкурсе разработчиков материалов "Taro x Mini Program Cloud Development".