Components
Components are an important part of building page elements. They allow you to create reusable elements and modules, similar to web components, with many possibilities worth exploring.
Functions
Functions are an important feature of JavaScript. Functions can be logic blocks used to control task execution and interruption.
Using functions as components is a very developer-friendly approach. It is not only simple, but also facilitates code reuse.
Here is a simple example:
import { FC } from 'gyron'
const HelloWorld = FC(() => {
return <div>Hello World</div>
})
HelloWorld is our function component, used to render a div element displaying "Hello world".
If you want to use this component in different places, you just need to import this component and use it like writing elements.
import { FC } from 'gyron'
const HelloWorld = FC(() => {
return <div>Hello World</div>
})
const App = FC(() => {
return (
<div class="container">
<HelloWorld />
</div>
)
})
Setup and Render
If you are using the scaffolding method or our babel plugin, you don't need to read this section. This is for third party developers.
Function components are divided into two areas, one is the setup scope, and the other is the render scope.
The setup scope is only executed once during component rendering. It will only be executed again after the component is destroyed and rendered again.
As the name suggests, the render scope is responsible for component updates, executed whenever dependent data changes or forceUpdate
is used for forced update.
If you look closely, you will find that the App component and the HelloWorld component are slightly different. The App component returns a function to render the page. Why can be found in the previous tutorial, which explains in detail.
Why Components Should Return a Function
When using destructuring in the setup
function of a stateful component
, special attention needs to be paid to props not being updated. To access props, use "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>
})
The highlighted part above is the setup area. The blocks in it will only be executed once, and then the states inside will be cached with closures for later updates, which will only update the render area.
import { useValue, FC } from 'gyron'
const HelloWorld = FC(() => {
const count = useValue(0)
function update() {
count.value++
}
return <div onClick={update}>counter {count.value}</div>
})
The highlighted part above is the render area, aka the html tags we are familiar with. It keeps updating when the component updates.
Auto Transform
We now also provide an option that relies on the @gyron/babel-plugin-jsx
plugin to work.
You just need to pass setup
as true
in the plugin options, and the plugin will automatically identify these two different modes, and convert all stateless components into stateful components, so you don't need to care whether the return value of the component is a function or 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>
)
})
After configuring the plugin, when defining a component with FC, the plugin will automatically identify and convert it to the code shown by the arrow below.
If you need to define methods, variables, etc. in the render area of the component, the component can return a function. In this case, the plugin will do nothing.
Props
Sometimes we want the component to be more versatile. Data may need to be passed to the component for different feedback. This is the role of Props.
The usage is the same as functions, but note that some internal parameter names have been occupied:
- children (for passing child components/elements to the component)
- isSSR (whether the current environment is in SSR, this parameter can be used to write more versatile code)
A common example:
const HelloWorld = ({ message }) => {
return <div>I Am {message}</div>
}
const App1 = <HelloWorld message="Legend" />
const App2 = <HelloWorld message="Legend 2" />
We pass a message parameter to the HelloWorld component. In the first case it will display "I Am Legend", and in the second case it will display "I Am Legend 2".
Data in components often flows in one direction, but sometimes child components need to change the data of the parent component. We recommend passing an update function to the child component, and then calling the update function in the child component to update the data.
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
Usually when writing a component, we need to listen to a props data in the setup area and then handle business logic. So we need to use the onBeforeUpdate
lifecycle hook in the component to determine if props have changed. Let's implement a simple example of getting user info by changing 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} />
})
The logic above is that when the token changes, request the /info
interface to get the user avatar and then render it. This is actually a high-frequency operation in business development. So we provide the useWatchProps
method to create a watch method and pass the props to listen to. Let's see how the code should be refactored:
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} />
})
The return value of useWatchProps
is a function. It automatically identifies the type of the first parameter 'token'
and the second parameter based on the type. You can also do:
interface InformationProps {
token: string
age: number
}
const Information = FC<InformationProps>(({ token, age }) => {
const watch = useWatchProps<InformationProps>()
watch(['token', 'age'], ([token, age]) => {
// ...
})
})
TypesScript
Gyron.js fully supports the TypesScript type system, but to introduce JSX syntax, wrapper functions like FC, FCA, and FCD are still needed. It does not change the behavior of the component at all, just provides type hints for prompting users when writing and using components.
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
Sometimes we need custom rendering or want to get the real element information. We can use the createRef
method to create an object that exposes a current
property, and then bind it to the corresponding component or element using ref.
import { createRef, onAfterMount, FC } from 'gyron'
const App = FC(() => {
const ref = createRef()
onAfterMount(() => {
console.log(ref.current)
// HTMLDivElement
})
return <div ref={createRef}></div>
})
Lifecycle Hooks
If you have read this article and just want to confirm the execution order of lifecycles, you can check out the lifecycle diagram in the Instance tutorial below.
When building a complex application, more logic is often needed to improve the application. For example, when requesting backend data or when the component is destroyed, some custom processing is required. Lifecycle hooks can help you complete these functions.
import { onAfterMount } from 'gyron'
const HelloWorld = ({ message }) => {
onAfterMount(({ $el }) => {
// Change the text color to blue after component rendering is complete
$el.style.color = 'blue'
})
return <div>I Am {message}</div>
}
Or remove timers in the component when the component is destroyed.
import { onDestroyed, FC } from 'gyron'
const HelloWorld = FC(({ message }) => {
const timer = setInterval(() => {
console.log(Date.now())
}, 1000)
onDestroyed(() => {
// Remove timer
clearInterval(timer)
})
return <div>I Am {message}</div>
})
When the component is destroyed, the timer
will be removed.
Or we can use Gyron.js to complete some high-frequency dom operations. For example, changing the position of elements without updating the dom in time. In previous programming patterns, the position of dom elements would be changed directly, resulting in bloated and unmaintainable code. With Gyron, these problems can be easily solved.
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>
)
})
We can tell the Gyron framework whether this data change needs to be automatically updated by changing the value of state.allowUpdate
. Thus, even if state.x
or state.y
changes, it is controllable.
Stateful Components and Stateless Components
After reading the Setup and Render section, you should have a general understanding of why there are stateless
components and stateful
components in Gyron.js.
A stateful
component refers to a component with internal data that is not refreshed during the next update. When the return value of a component is a function and the return value of the function is a node, we call it a stateful
component.
import { useValue, FC } from 'gyron'
const HelloWorld = FC(() => {
const count = useValue(0)
return <div>{count}</div>
})
In some cases, you just want to encapsulate some UI components whose states all come from the parent component, that is, they don't need to manage these states internally, just manage the UI presentation and respond to user events properly. Stateless
components have better migration capabilities.
When a component directly returns a node, it is a stateless
component. Stateless
components cannot have internal states. The function will be executed once again on each update. If you need to retain the internal state of the component, please use a stateful
component.
import { useValue, FC } from 'gyron'
const HelloWorld = FC(({ count }) => {
return <div>{count}</div>
})
Cache Components
Cache components can automatically maintain the state of components. When the component is invisible, the state of the component will be retained. When the component is activated again, the state will be automatically restored.
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>
})
)
When the props of a cache component change, the cache component will automatically update. When the dependent data of the component changes, the component will also automatically update.
You can optimize components with the keepComponent method. Do not overuse keepComponent components, because this uses a Map internally to save these components. If the cache component is determined to no longer be used, you can use the clearCacheComponent
method to clear the local cache and disconnect the component's dependencies.
import { clearCacheComponent, keepComponent, FC } from 'gyron'
const App = keepComponent(
FC(() => {
// ...
})
)
clearCacheComponent(App)
Async Components
Async components can be used for lazy loading of pages to reduce the resources needed to load on the homepage.
Async components can also do some other things that will be covered in detail in the advanced tutorials. Async Components
FCD
FCD
is used to define components with deferred updates. As the name implies, components defined with FCD, updates will not block UI rendering and user behavior.
It is used the same way as FC. Define a component with FCD.
const SlowItem = FCD<{ text: string }>(({ text }) => {
const startTime = performance.now()
while (performance.now() - startTime < 1) {
// ...
}
return <li>Text: {text}</li>
})
When our component update is very time consuming, FCD can be used to define the component. Let's look at the difference between FC and FCD with two practical examples below.
In the example above, a component is defined with FCD. Each time it is modified, the update task of SlowItem
will enter a deferred update queue. If the tasks in this queue exceed 5ms, the next update will enter an idle queue.
In comparison, it can be found that in the first example, the user's input is always responded by the browser at any time, but in the second example, each modification by the user may cause the UI to freeze.