foreword
Hello everyone, I'm webfansplz . First the good news to share with you, I joined VueUse team you, thank @antfu invitation, very happy to be a team today to talk with you VueUse of Design and implementation.
introduce
We all know that Vue3 introduces a composite API, which greatly improves the ability of logic reuse. VueUse implements many easy-to-use, practical and interesting functions based on the composite API. For example:
useMagicKeys
useMagicKeys monitors the key state and provides the function of combining hot keys, which is very magical and interesting. Using it, we can easily monitor the number of times we use CV Dafa :)
useScroll
useScroll provides some responsive states and values, such as scroll state, arrival state, scroll direction, and current scroll position.
useElementByPoint
useElementByPoint is used to obtain the topmost element at the current coordinate position in real time. With useMouse , we can do some interesting interactions and effects.
user experience
User experience
VueUse has achieved a great user experience for both users and developers. Let's take a look at the user experience first:
Strong type support
VueUse is written in TypeScript and comes with complete TS documentation, with good TS support capabilities.
SSR support
We have friendly support for SSR, which works very well for server-side rendering scenes.
Ease of use
For some functions that support incoming configuration options, we will provide users with a set of common default options, which can ensure that users do not need to pay too much attention to your function implementation and details in most application scenarios. Take useScroll as example:
<script setup lang="ts">
import { useScroll } from '@vueuse/core'
const el = ref<HTMLElement | null>()
// 只需传入滚动元素就可以工作
const { x, y } = useScroll(el)
// 节流支持选项
const { x, y } = useScroll(el, { throttle: 200 })
</script>
useScroll provides throttling options for some developers who have performance requirements. But we hope that users only pay attention to this configuration when they need it, because when there are too many configuration parameters, it is necessary to understand the meaning and configuration of the parameters. It is a mental burden. In addition, the general default configuration is actually a manifestation of the ability to open the box!
Working with documentation
We provide an interactive Demo and a simplified Usage for the documentation. Users can learn more about the functions by playing the Demo, or they can use the functions easily by copying the Usage through CV Dafa. Really fragrant!
compatibility
We mentioned earlier that Vue3 introduced the concept of composite API, but thanks to the implementation of the composition-api plugin, we can also use composite API in Vue2 projects. In order to allow more users to use VueUse, Anthony Fu implemented vue-demi , which judges the user's installation environment (Vue2 project references composition-api
plugin, Vue3 project references official package), so that Vue2 users can also use VueUse
, Nice!
developer experience
Directory Structure
Based on Monorepo
, the project adopts a flat directory structure, which is convenient for developers to find corresponding functions.
We have created a separate folder for the implementation of each function, so that when developers fix bugs and add new functions, they only need to pay attention to the implementation of specific functions under the folder, and do not need to pay attention to the details of the implementation of the project itself. The cost of getting started is greatly reduced. The writing of Demo and documents is also completed in this folder, avoiding the bad R&D experience of repeatedly jumping up and down to find directory structure files.
Contribution Guidelines
We provide a very detailed contribution guide to help developers who want to contribute to get started quickly and write some automation scripts to help developers avoid some manual work.
Atomic CSS
The project uses atomized CSS as the CSS writing scheme. I personally think that atomized CSS can help us quickly write a demo demo, and the demo of each function is independent and uncoupled, and will not cause the mental burden of abstract reuse.
design thinking
composable functions
A composable function simply means that a compositional relationship can be established between functions, for example:
The implementation of useScroll combines three functions, and combines functions with a single responsibility to form another function to achieve the ability of logical reuse. I think this is the charm of combined functions. Of course, each function It can also be used independently, and users can choose according to their own needs.
Developers can achieve better separation of concerns when dealing with functional functions. For example, when dealing with useScroll , we only need to pay attention to the implementation of the scroll function, and do not need to pay attention to the internal logic and implementation of anti-shake throttling and event binding. .
Create a "link"
Anthony Fu shared such a pattern at Vue Conf 2021:
- Create an input->output link
- The output will automatically change as the input changes
We link data and logic when writing composable functions so that we don't have to worry about how and when to update the data. For example:
<script setup lang="ts">
import { ref } from 'vue'
import { useDateFormat, useNow } from '@vueuse/core'
const now = useNow() // 返回一个ref值
const formatted = useDateFormat(now) // 将数据传入与逻辑建立连结
</script>
// useDateFormat实现
function useDateFormat(date, formatStr = 'HH:mm:ss') {
return computed(() => formatDate(normalizeDate(unref(date)), unref(formatStr)))
}
From the above example, we can see that useDateFormat uses a computed property to wrap the input in the internal logic, so that we can make the output automatically change according to the input change, and the user only needs to pass in a responsive value , do not need to pay attention to the specific update logic.
Use ref
instead reactive
whenever possible
ref
and reactive
have their own advantages and disadvantages, here I mainly talk about my personal views from the user's point of view:
// reactive
function useScroll(element){
const position = reactive({ x: 0, y: 0 });
// impl...
return { position,...}
}
// 解构丢失响应性
const { position } = useScroll(element)
// 用户需手动toRefs保持响应性
const { x, y } = toRefs(position)
// ref
function useScroll(element){
const x = ref(0);
const y = ref(0);
// impl...
return {x,y,...}
}
// 不会丢失响应性,用户可直接拿来渲染,watch..
const { x, y } = useScroll(element)
From the above example, we can see that if we use reactive
, the user needs to consider the problem that destructuring will lose responsiveness, which also limits the user's freedom to use destructuring and reduces the ease of use of this function to a certain extent. .
Some people may complain about the use of ref
of .value
. In fact, in most cases, we can reduce its use through some tricks:
unref
API
const x = ref(0)
console.log(unref(x)) // 0
- Unpack
ref
withreactive
const x = ref(0)
const y = ref(0)
const position = reactive({x, y})
console.log(position.x, position.y) // 0 0
Reactivity Transform
still in the experimental stage
<script setup>
let count = $ref(0)
count++
</script>
Use options object as parameter
When implementing a function, if there is an option parameter scenario, we usually recommend that developers use objects as input parameters, for example:
// good
function useScroll(element, { throttle, onScroll, ...}){...}
// bad
function useScroll(element, throttle, onScroll, ....){...}
You can clearly see the difference between the two. There is no doubt that the first way of writing will be more scalable, and it is not easy to cause some destructive changes to the function itself in subsequent iterations.
Documentation implementation
I won't go into details about the specific implementation of the function. After all, we have as many as 200 😝 . Here I will share with you the more interesting implementation of the VueUse
construction document, which I think is great.
document composition
Let's first look at the components of the next function function document:
build process
VueUse uses VitePress as a document building tool, let's take a look at the more interesting parts:
- Start the VitePress service with the packages folder as the entry
VitePress uses conventional routing (file is routing), so accessing http://xxx.com/core/onClickOutside
will actually parse our corresponding index.md
file. When you see this, you will have doubts. index.md
file only contains usage
, where is the other information? Where is it? Here comes the fun part~
- Write Vite plugin
MarkdownTransform
to process Markdown files:
export function MarkdownTransform(): Plugin {
return {
name: 'vueuse-md-transform',
enforce: 'pre',
async transform(code, id) {
if (!id.endsWith('.md'))
return null
const [pkg, name, i] = id.split('/').slice(-3)
if (functionNames.includes(name) && i === 'index.md') {
// 对index.md进行处理
// 使用拼接字符串的方式拼接Demo,类型声明,贡献者信息和更新日志
const { footer, header } = await getFunctionMarkdown(pkg, name)
if (hasTypes)
code = replacer(code, footer, 'FOOTER', 'tail')
if (header)
code = code.slice(0, sliceIndex) + header + code.slice(sliceIndex)
}
return code
},
}
}
Through the processing of this Vite plug-in, our document part is complete. Here is another question, where did the contributor's data and the change log data come from? The two data processing methods are similar, I will take one of them To illustrate the implementation:
- Get git committer info
import Git from 'simple-git'
export async function getContributorsAt(path: string) {
const list = (await git.raw(['log', '--pretty=format:"%an|%ae"', '--', path]))
.split('\n')
.map(i => i.slice(1, -1).split('|') as [string, string])
return list
}
We read the information of the relevant file submitters through the simple-git
plug-in. After we have the data, how do we render them into the page? We still use the Vite plug-in, but this time we need to register virtual modules.
- Register virtual modules
const ID = '/virtual-contributors'
export function Contributors(data: Record<string, ContributorInfo[]>): Plugin {
return {
name: 'vueuse-contributors',
resolveId(id) {
return id === ID ? ID : null
},
load(id) {
if (id !== ID) return null
return `export default ${JSON.stringify(data)}`
},
}
}
Just pass in the data we just got when registering the virtual module, and then we can introduce the virtual module into the component to access the data.
- usage data
<script setup lang="ts">
import _contributors from '/virtual-contributors'
import { computed } from 'vue'
const props = defineProps<{ fn: string }>()
const contributors = computed(() => _contributors[props.fn] || [])
</script>
After getting the data, we can render the page. This is the implementation principle of the Contributors and Changelog sections in the document. Let's take a look at the effect:
After reading this, do you think it is quite interesting, the Vite plug-in can actually be used to do a lot of things.
V8.0 is coming🎉
We officially released V8.0 two days ago, which mainly brought:
- Normalized the names of some functions and used aliases for backward compatibility
- Several new functions have been added, and the number of functions has now reached 200+
@vueuse/core/nuxt
=>@vueuse/nuxt
- Instruction support for some functions, welcome to use
Epilogue
Finally, thanks to Anthony Fu for his corrections and suggestions on this article, Ruisbai! If my article is helpful to you, please follow me at and learn together at .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。