The cause of
Recently, I had the honor to participate in the development of our department’s public component library based on VUE3. I would like to record my experience in the actual process of using VUE3 and some problems to pay attention to in the development of public components.
The functionality to be implemented
attribute
The event
The effect after implementation
Theory development process
We use the test Driven Development (TDD) development process as
- Document corresponding function points
- Write unit tests corresponding to function points
- Run test cases to make sure they fail
- Write code to implement
- Run test cases to ensure success
Actual development process
Knowledge points that need to be mastered before development
- New features for Vue3
- Test vUE components with jEST, @vue/test-utils: 2.0.0-rC.6
- JSX grammar
The structure of the organization
Project organization Structure
Refer to the Vitepress documentation for organizational structure
Document corresponding function points
Mainly according to the UI renderings given by designers, combined with other excellent UI library function points, finally discuss the effect we need to achieve, finally write a document.
Test case writing
Four metrics for test coverage
Line coverage: Is every executable line executed? Function coverage: Is every function called? Branch coverage: Are all branches of each process control implemented? Statement Coverage: Is every statement executed?Copy the code
How do I write test cases
- Test-driven development requires test case writing to take precedence over the implementation of unit functionality, which requires thinking about how components should be broken down and what functionality each part will need after being broken down.
- Test these imaginary features. However, in the actual development process, it is difficult to achieve a relatively high test coverage by writing test cases before the function is developed, so we have to make a supplement to the test after the function is developed.
Pagination component structure
Here is the organizational structure I gave before writing the feature, which is implemented in JSX files like Jumper, Pager, Pagination, simpler, Sizes, Total, etc
- _tests_
- pagination.js
- style
- index.js
- index.less
- mixin.less
- const.js
- index.js
- jumper.jsx
- pager.jsx
- pagination.jsx
- simpler.jxs
- sizes.jsx
- total.jsx
Copy the code
Write sample test cases for the Jumper function
The rest of the test was similar
- If the ShowQuickJumper tree is true, the jumper function is displayed, and whether or not jumper shows a particular jumper class that the rendered component has. The classes API in @vue/test-utils does this very well.
- Jumper is a test for problems such as when the jumper input is not a number, the number is out of bounds, the number is NaN, and so on.
- Function test whether you can jump to the corresponding page when you lose focus after typing.
Test coverage achieved
Before the function is done
Failed all the tests
Once the function is done
Less than 70% test coverage, unfortunately forgot screenshots
After the test cases are added
As shown below, the final test coverage is 100%
Summary of tests
Is it necessary to pursue 100% test coverage?
It was really necessary, because in the pursuit of 100% test coverage I went back and looked at every line of code to see if I could actually cover every line of code, and I found some redundant code in the process, For example, I’ve done a bit of processing in jump.jsx to send back pagination.jsx data to make sure it doesn’t generate non-numbers and doesn’t go out of bounds, It was processed again in Pagination. JSX, which made the processing in Pagination redundant and never executed. Because each line, each branch can be executed, this also reduces potential bugs.
Problems encountered during function implementation
Style specification
- Need to be compatible with switching style, later may adjust font color, button size, distance between buttons, etc., all colors, some conventional distance, etc., need to be taken from the public file during development. So each little style needs to be considered whether it is necessary, whether it needs to be fetched in the library, and the development process may be slower than writing colors and so on.
- Therefore, all possible classes must have the IS attribute, such as IS-disabled or IS-double-jumper
- Try not to use element selectors, one is inefficient, one is easy to change the impact of the required
Js specification
- Handlexxx. bind(null, aaa) handlexxx. bind(null, aaa)
- JSX statements should be formatted and short
Return (<div> {simple.value? <Simple XXX /> : <Pager XXX />} <div>) const renderPage = () => {if (Simple. Value) {return <Simele XXX />; } return <Pager xxx/> } return ( <div> {renderPage()} </div> )Copy the code
- The functionality of the functionality is encapsulated as hooks as possible, such as implementing v-Models
Actual use of vuE3’s new features
The setup parameters
The setup function takes two arguments, props and context
Props parameters
Es6 can not be used for deconstruction, because the deconstructed data will lose responsiveness, and toRef or toRefs will be used for processing
The context parameter
This parameter includes attrs, slot, and emit. If an emit is required, it must be declared in the emits configuration
import { definedComponent } from 'vue'; export default definedComponent({ emits: ['update:currentPage'], setup(props, { emit, slot, attrs }) { emit('update:currentPage'); . }})Copy the code
V – model is used
The pageSize and currentPage properties both need v-Model implementation.
Vue2 implements bidirectional binding
Vue 2 implements custom components from one to multiple V-Model two-way data binding methods
Vue3 implements bidirectional binding
Implement binding of individual data
If you only want to implement currentPage bidirectional binding, vue3 binds modelValue by default
<Pagination :modelValue="currentPage" @update:modelValue="(val) => {currentPage = val}" /> / Pagination import {defineComponent} from vue; export default defineComponent({ props: { currentPage: { type: Number, default: 1 } } emits: ['update:currentPage'], setup(props, {emit}) {when Pagaintion component changes, Emit emit(' UPDATE :currentPage', newCurrentPage)}})Copy the code
Implement multiple data binding
The pageSize and currentPage properties both need v-Model implementation
<Pagination v-model:currentPage="currentPage" V-model :pageSize="pageSize" /> defineComponent } from vue; export default defineComponent({ pageSize: { type: Number, default: 10, }, currentPage: { type: Number, default: 1, }, emits: ['update:currentPage', 'update:pageSize'], setup(props, {emit}) {when Pagaintion component changes, Emit (' UPDATE :currentPage', newCurrentPage) emit(' Update :pageSize', newPageSize)}}) emit(' Update :pageSize', newPageSize)}}Copy the code
Comparison of Vue3 and Vue2 V-model
There is little difference in the convenience of binding a single attribute, but binding multiple values can obviously feel the benefits of Vue3. There is no need to use Sync and computed to trigger such a weird writing method, and it is easier and easier to maintain by using V-Model.
Actual use of Reactive ref toRef toRefs
Add reactive to objects using Reactive
It is not used in actual components. Here is an example
import { reactive } from 'vue'; // Setup const data = reactive({count: 0}) console.log(data.count); / / 0Copy the code
Add a response to the value type data using ref
The jumper file is used to add responsiveness to data entered by the user
import { defineComponent, ref, } from 'vue'; export default defineComponent({ setup(props) { const current = ref(''); return () => ( <div> <FInput v-model={current.value}></FInput> </div> ); }});Copy the code
Of course, it is also possible to use ref to add a response to an object, but each time you use it, you need to add a value, such as data.value.count. The data returned by ref is an object with a value attribute, which is essentially a copy of data. It has no relation to the original data. Changing the value returned by ref no longer affects the original data
import { ref } from 'vue'; // Setup const data = ref({count: 0}) console.log(data.value.count); / / 0Copy the code
toRef
- ToRef is used to create a new REF for an attribute on a source responsive object, thus maintaining a reactive connection to its source object attribute. It takes two parameters: the source responsive object and the attribute name, and returns a ref data, which is essentially a reference to a value that changes as well as the original value
- The actual components are not used, as illustrated below
import { toRef } from 'vue'; export default { props: { totalCount: { type: number, default: 0 } }, setup(props) { const myTotalCount = toRef(props, totalCount); console.log(myTotalCount.value); }}Copy the code
toRefs
ToRefs is used to convert a reactive object into a result object, where each attribute of the result object is a REF pointing to the corresponding attribute of the original object. This is often used in ES6 deconstruction assignments, because when a reactive object is directly deconstructed, the deconstructed data is no longer reactive, and toRefs is a convenient way to solve this problem. Essentially a reference to a value that changes the original value
// Setup const {small, pageSizeOption, totalCount, simple, showSizeChanger, showQuickJumper, showTotal, } = toRefs(props); Console. log(small.value)Copy the code
Because props cannot be deconstructed with ES6, they must be handled with toRefs
Four different ways to add responsiveness to data
Is a reference to the original value
Reactive, toRef, and toRefs handle that the object returned is a reference to the original object. If the object is changed in response, the original object will also change. Ref is a copy of the original object and has no relationship with the original object.
How to value
Reactive objects returned by ref, toRef, and toRefs all require value, but reactive does not
On what data
Reactive is the common object. Ref value type data; ToRef responsive object to retrieve a property; ToRefs deconstruct responsive objects;
Replace mixins with vue3 hooks
If reuse is a function, VUe2 may adopt the method of mixins. One disadvantage of mixins is that when there are multiple mixins, it is not known which mixins the method comes from. Vue3 hooks fix this very well. Let’s write the V-model as a hook
const useModel = ( props, emit, config = { prop: 'modelValue', isEqual: false, }, ) => { const usingProp = config? .prop ?? 'modelValue'; const currentValue = ref(props[usingProp]); const updateCurrentValue = (value) => { if ( value === currentValue.value || (config.isEqual && isEqual(value, currentValue.value)) ) { return; } currentValue.value = value; }; watch(currentValue, () => { emit(`update:${usingProp}`, currentValue.value); }); watch( () => props[usingProp], (val) => { updateCurrentValue(val); }); return [currentValue, updateCurrentValue]; }; // Import useModel from '... // setup const [currentPage, updateCurrentPage] = useModel(props, emit, {prop: 'currentPage',});Copy the code
And you can see, we can clearly see where the currentPage, the updateCurrentPage came from. It is easy and quick to reuse, and V-models like Pager and SIMPLER pages can use this hook
Computed, watch
The feel is similar to that used in Vue2, except that it needs to be introduced when used in Vue3. The change event needs to be triggered when currentPage changes, so you need to use the Watch function
import { watch } from 'vue'; Const [currentPage, updateCurrentPage] = useModel(props, emit, {prop: 'currentPage',}); // Setup const [currentPage, updateCurrentPage] = useModel(props, emit, {prop: 'currentPage',}); watch(currentPage, () => { emit('change', currentPage.value); })Copy the code
Reference documentation
- Understand the Test Coverage Report