组件
组件是构建页面元素的重要部分,它允许您创建可重用的元素和模块,与 web component 类似,可以做的事情有很多,这部分值得你的探索。
函数
函数是一个 javascript 的重要功能,函数可以是一组用来控制任务执行和中断的逻辑块。
将函数用作组件是一个非常贴近开发者思维的作法,它不仅简单,还便于代码复用。
下面介绍一个简单的例子。
import { FC } from 'gyron'
const HelloWorld = FC(() => {
return <div>Hello World</div>
})
HelloWorld 就是我们的函数组件,用来渲染一个 div 元素,显示的内容就是 Hello world。
如果你想将这个组件用在不同的地方,那么你只需要导入这个组件然后像写元素一样使用即可。
import { FC } from 'gyron'
const HelloWorld = FC(() => {
return <div>Hello World</div>
})
const App = FC(() => {
return (
<div class="container">
<HelloWorld />
</div>
)
})
Setup 和 Render
如果你是按照脚手架的方式或者使用了我们的 babel plugin 就不需要再阅读这块文档,如果你是第三方开发者可能需要阅读这块内容。
函数组件分为两个区域,一个是 setup scope,另外一个是 render scope。
setup scope 区域在组件渲染的流程中只会被执行一次,只有在销毁组件后再次渲染才会被执行。
顾名思义,render scope 就是负责组件更新,只要组件依赖的数据发生变更或者使用forceUpdate
强制更新时执行。
如果你够仔细,那么你会发现 App 组件和 HelloWorld 组件不太一样,App 组件返回了一个函数用来渲染页面,为什么可以参考前面一篇教程,里面解释的很详细。
在有状态组件
的setup
函数中使用解构的时候需要特殊注意,props 是不会得到更新,如果要访问 props 需要使用“props.xxxxx”。
import { useValue, FC } from 'gyron'
const HelloWorld = FC(() => {
const count = useValue(0)
function update() {
count.value++
}
return <div onClick={update}>counter {count.value}</div>
})
上述高亮行部分就是 setup 区域。其中的块只会被执行一次,然后其中的状态会用闭包缓存下来,再下一次更新时只会更新 render 区域。
import { useValue, FC } from 'gyron'
const HelloWorld = FC(() => {
const count = useValue(0)
function update() {
count.value++
}
return <div onClick={update}>counter {count.value}</div>
})
上述高亮行部分就是 render 区域。也就是我们所熟知的 html 标签,当组件发生更新时这个区域会一直更新。
自动转换
现在我们也提供一种选项,但是这个是基于@gyron/babel-plugin-jsx
插件才能生效。
你只需要在插件选项中传递setup
为true
,那么插件会自动识别这两种不同的模式,然后将所有无状态组件转换为有状态组件,完全就不需要关注这个组件的返回值是函数还是JSX.Element
了。
import esbuild from 'esbuild'
import { babelESBuildJsx } from '@gyron/babel-plugin-jsx'
esbuild.build({
// ...
plugins: [
babelESBuildJsx({
setup: true,
}),
],
})
import { FC } from 'gyron'
const App = FC<{ msg: string }>((props) => {
const count = useValue(0)
function onClick() {
count.value++
}
return (
<div onClick={onClick}>
{props.msg} - {count.value}
</div>
)
})
// ↓ ↓ ↓ ↓ ↓
const App = FC<{ msg: string }>((props) => {
// ...
return (props) => (
<div onClick={onClick}>
{props.msg} - {count.value}
</div>
)
})
在配置完插件后使用 FC 定义一个组件,然后插件就会自动识别转换成箭头下方的所示代码。
如果你需要在 render 区域定义一些方法或者变量之类的对象,那么组件可以返回一个函数。这个时候插件将什么都不会处理。
参数(Props)
有的时候我们想让组件变得更通用,可能需要传递数据到组件之后有不同的反馈,这就是 Props 的作用。
使用的方式也和函数一样,但是注意,有几个内部的参数名已经被占用:
- children (用来向组件传递子组件/元素)
- isSSR (当前环境是否处于 SSR 中,可以用这个参数编写更通用的代码)
一个常用例子。
const HelloWorld = ({ message }) => {
return <div>I Am {message}</div>
}
const App1 = <HelloWorld message="Legend" />
const App2 = <HelloWorld message="Legend 2" />
我们向 HelloWorld 组件传递了一个 message 参数,在第一个场景下会展示I Am Legend
,在第二个场景下会展示I Am Legend 2
。
数据在组件中往往都是单向流动的,但是有些场景需要在子组件中改变父组件的数据,我们推荐传递一个 update 函数给子组件,然后子组件调用 update 函数更新数据。
import { useValue, FC } from 'gyron'
const HelloWorld = ({ message, update }) => {
return <div onClick={update}>I Am {message}</div>
}
const App = FC(() => {
const name = useValue('Legend')
return (
<div class="container">
<HelloWorld
message={name.value}
update={() => (name.value = 'Legend 2')}
/>
</div>
)
})
useWatchProps
通常情况下,在编写一个组件时,我们需要在 setup 区域监听一个 props 数据然后处理业务逻辑,那我们就需要在组件中使用onBeforeUpdate
生命周期勾子,来判断 props 是否发生了变更,我们简单实现一个根据 token 变更然后换取用户信息。
import { FC, onAfterUpdate } from 'gyron'
interface InformationProps {
token: string
}
const Information = FC<InformationProps>(({ token }) => {
const avatar_url = useValue('')
onAfterUpdate((perv: InformationProps, cur: InformationProps) => {
if (perv.token !== cur.token) {
if (cur.token) {
fetch(`/info?token=${cur.token}`).then(({ url }) => {
avatar_url.value = url
})
}
}
})
return <img src={avatar_url} />
})
上述代码的逻辑就是当 token 发生变化的时候请求/info
接口获取用户头像然后渲染。这种在业务开发中其实是一个高频操作,于是我们提供了useWatchProps
方法,用此方法创建一个 watch 方法,然后传递需要监听的 props。接下来我们看看代码应该怎么改造
import { FC, useWatchProps } from 'gyron'
interface InformationProps {
token: string
}
const Information = FC<InformationProps>(({ token }) => {
const avatar_url = useValue('')
const watch = useWatchProps<InformationProps>()
watch('token', (token) => {
fetch(`/info?token=${token}`).then(({ url }) => {
avatar_url.value = url
})
})
return <img src={avatar_url} />
})
useWatchProps
的返回值就是一个函数,并且会根据类型自动识别出第一个参数'token'
和第二个参数的类型,你还可以这样
interface InformationProps {
token: string
age: number
}
const Information = FC<InformationProps>(({ token, age }) => {
const watch = useWatchProps<InformationProps>()
watch(['token', 'age'], ([token, age]) => {
// ...
})
})
TypesScript
Gyron.js 是完全支持 TypesScript 类型系统的,但是要接入 JSX 语法,还需要用包裹函数 FC、FCA、FCD 等,它完全不改变组件的行为,只是提供了一个类型提示,在编写组件和使用组件的时候可以提示用户。
import { FC } from 'gyron'
interface HelloWorldProps {
message: string
update: () => void
}
const HelloWorld = FC<HelloWorldProps>(({ message, update }) => {
return <div onClick={update}>I Am {message}</div>
})
Ref
有的时候我们需要自定义渲染,或者想获取到真实的元素信息。我们可以使用createRef
方法创建一个对象,它会暴露一个current
属性,然后在对应的组件或元素上使用 ref 进行绑定。
import { createRef, onAfterMount, FC } from 'gyron'
const App = FC(() => {
const ref = createRef()
onAfterMount(() => {
console.log(ref.current)
// HTMLDivElement
})
return <div ref={createRef}></div>
})
生命周期钩子
已经阅读过这篇文章,只是想确定生命周期执行的顺序,可以去查看实例这篇教程后面的生命周期图。
在制作一款复杂的应用时,往往需要更多的逻辑完善应用。比如,我们在请求后端数据时或者当组件销毁时需要一些自定义处理,就可以使用生命周期钩子帮助你完成这些功能。
import { onAfterMount } from 'gyron'
const HelloWorld = ({ message }) => {
onAfterMount(({ $el }) => {
// 在组件渲染完成之后将文字颜色改变成蓝色
$el.style.color = 'blue'
})
return <div>I Am {message}</div>
}
或者在组件销毁时删除组件内的定时器。
import { onDestroyed, FC } from 'gyron'
const HelloWorld = FC(({ message }) => {
const timer = setInterval(() => {
console.log(Date.now())
}, 1000)
onDestroyed(() => {
// 移除定时器
clearInterval(timer)
})
return <div>I Am {message}</div>
})
当组件销毁时,就会移除timer
定时器。
再或者我们用 Gyron.js 完成一些高频 dom 操作,比如,改变元素的位置,但不需要及时更新 dom。通常在以前的编程模式中会直接改变 dom 元素的位置,但这会造成代码的臃肿和不可维护,如果使用 Gyron,那么这些问题可以迎刃而解。
import { useReactive, onBeforeUpdate, FC } from 'gyron'
const HelloWorld = FC(({ message }) => {
const state = useReactive({
allowUpdate: false,
x: 0,
y: 0,
})
onBeforeUpdate(() => {
return state.allowUpdate
})
return (
<div
style={{
position: 'absolute',
left: position.x + 'px',
top: position.y + 'px',
}}
>
I Am {message}
</div>
)
})
我们可以通过改变state.allowUpdate
的值来告诉 Gyron 框架,此次数据变更是否需要自动更新。这样,即使state.x
或者state.y
发生变更也是可控的。
有状态组件 和 无状态组件
在阅读完 Setup 和 Render 一栏后就应该有一个大概的认知,在 Gyron.js 中为什么会有无状态
组件和有状态
组件。
有状态
组件是指一个组件拥有内部数据,在下次更新时这些数据不被刷新。当一个组件的返回值是一个函数时,并且函数的返回值是一个节点时,那么我们就称之为有状态
组件。
import { useValue, FC } from 'gyron'
const HelloWorld = FC(() => {
const count = useValue(0)
return <div>{count}</div>
})
有些情况下只想封装一些 UI 组件,他们的所有状态全部来自父组件,也就是说自己内部不需要管理这些状态,只需要管理好 UI 表现和响应用户事件。无状态
组件具有更好的迁移特性。
当组件直接返回一个节点时,那这个组件就是无状态
组件。无状态
组件无法拥有内部状态,在每一次更新都会重新执行一次函数,如果需要保留组件内部的状态,请使用有状态
组件。
import { useValue, FC } from 'gyron'
const HelloWorld = FC(({ count }) => {
return <div>{count}</div>
})
缓存组件
缓存组件可以自动保持组件的状态,当组件不可见时,组件的状态会一直保留,当下次激活组件时,状态自动还原。
import { keepComponent, useValue, FC } from 'gyron'
const App = keepComponent(
FC(() => {
const count = useValue(0)
function increment() {
count.value++
}
return <div onClick={increment}>Cache Component {count.value}</div>
})
)
当缓存组件 Props 发生变更时,缓存组件会自动更新,当组件依赖的数据发生变更时组件也会自动更新。
你可以使用 keepComponent 方法对组件进行优化,请勿过度使用 keepComponent 组件,因为这在内部使用了一个 Map 保存这些组件,如果缓存组件确定不再使用,你可以使用clearCacheComponent
方法清空本地缓存并且断开组件的依赖关系。
import { clearCacheComponent, keepComponent, FC } from 'gyron'
const App = keepComponent(
FC(() => {
// ...
})
)
clearCacheComponent(App)
异步组件
异步组件可以用在页面的懒加载上面,以减少首页需要加载的资源。
异步组件还可以做一些其它的事情,高级教程中会详细介绍。异步组件
FCD
FCD
是用来定义延迟更新组件。顾名思义,用 FCD 定义的组件,更新不会阻塞 UI 的渲染和用户的行为。
使用方法和 FC 一样,用 FCD 定义一个组件。
const SlowItem = FCD<{ text: string }>(({ text }) => {
const startTime = performance.now()
while (performance.now() - startTime < 1) {
// ...
}
return <li>Text: {text}</li>
})
当我们组件更新很耗时的情况下,可以使用 FCD 来定义组件。下面用两个实际例子来看看 FC 和 FCD 的区别。
上面那个例子是使用 FCD 定义了一个组件,然后每修改一次,SlowItem
的更新任务都会进入一个延迟更新队列,这个队列里面的任务更新超过 5ms 那么下次更新会进入一个空闲队列。
对比可以发现,第一个例子中用户的输入时刻都会被浏览器响应,但是在第二个例子中用户每修改一次都有可能导致 UI 卡顿。