mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
add composables documentation
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Composables
|
||||
|
||||
Composables are way of splitting up your code into distinct, reusable chunks.
|
||||
They can replace providers, mixins and more. Any code you can put into a component, can also be written as a composable.
|
||||
Using them effectively can make your code more reusable, decoupled, and easier to follow.
|
||||
|
||||
## Using Composables in the Composition API
|
||||
|
||||
Example: accessing the current user from the store
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useCurrentUser } from "composables/user";
|
||||
|
||||
const { currentUser } = useCurrentUser();
|
||||
</script>
|
||||
```
|
||||
|
||||
You can now access the current user with `currentUser.value`.
|
||||
|
||||
## Using Composables in the Options API
|
||||
|
||||
Composables are not limited to the composition api. This is the same example from above, using the options api.
|
||||
|
||||
```vue
|
||||
<script>
|
||||
import { useCurrentUser } from "composables/user";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { currentUser } = useCurrentUser();
|
||||
return { currentUser };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
You can now access the current user with `this.currentUser` from anywhere within the component.
|
||||
|
||||
## Testing Components with Composable Stores
|
||||
|
||||
When writing a test which includes a component that has a composable store (like useCurrentUser),
|
||||
there are two ways to test it.
|
||||
|
||||
### Mocking the store
|
||||
|
||||
You can provide the store in the mount function as follows:
|
||||
|
||||
```js
|
||||
const wrapper = shallowMount(TestedComponent,
|
||||
localVue,
|
||||
provide: { store },
|
||||
});
|
||||
```
|
||||
|
||||
`store` must be a Vuex store.
|
||||
The `mockModule` helper can help creating a store for the required modules:
|
||||
|
||||
```js
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user: mockModule(userStore),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking the composable
|
||||
|
||||
The second option is to mock the composable:
|
||||
|
||||
```js
|
||||
import { useCurrentUser } from "composables/user";
|
||||
|
||||
jest.mock("composables/user");
|
||||
useCurrentUser.mockReturnValue({
|
||||
currentUser: {}
|
||||
});
|
||||
```
|
||||
|
||||
While simpler in this example, you may need to manually mock more return values and composables than the other method, depending on the composables the component is using.
|
||||
|
||||
## Using Composables for more than Stores
|
||||
|
||||
Composables can be of great use to extract any reactive code from your components. For an example of this, take a look at [userFilterObjectArray](https://github.com/galaxyproject/galaxy/blob/dev/client/src/composables/utils/filter.js).
|
||||
|
||||
Usage:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useFilterObjectArray } from "composables/utils/filter";
|
||||
|
||||
const filteredArray = useFilterObjectArray(
|
||||
someReactiveArray,
|
||||
searchValue,
|
||||
["name", "description"]
|
||||
);
|
||||
</script>
|
||||
```
|
||||
|
||||
It's a simple filtering function, but fully reactive.
|
||||
Whenever any of the inputs changes, the return value is re-computed, without having to call the function again.
|
||||
|
||||
## Further Reading
|
||||
|
||||
* [Composition API](https://vuejs.org/api/composition-api-setup.html)
|
||||
* [\<script setup\>](https://vuejs.org/api/sfc-script-setup.html)
|
||||
@@ -1,3 +1,7 @@
|
||||
**Notice** Consider using [Composables](composalbes.md) instead of Providers. They offer more functionality and need less boilerplate.
|
||||
|
||||
---
|
||||
|
||||
We are using components in two very distinct ways. The first, "normal", kind of component will
|
||||
probably look familiar to anybody whis is already passingly familiar with Vue. Here the relevant
|
||||
information comes in as properties, any internal variables get defined in "data", changes go out as
|
||||
@@ -27,7 +31,7 @@ restriction that Vue needs a single root element in which to render.
|
||||
<template>
|
||||
<AutoComplete
|
||||
:options="options"
|
||||
:value="doodad.category"
|
||||
:value="doodad.category"
|
||||
@select="saveCategory"
|
||||
/>
|
||||
</template>
|
||||
@@ -35,7 +39,7 @@ restriction that Vue needs a single root element in which to render.
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
props: {
|
||||
doodad: { type: Object, required: true },
|
||||
options: { type: Array, required: true },
|
||||
},
|
||||
@@ -51,7 +55,7 @@ export default {
|
||||
|
||||
This component accepts a mandatory input object (doodad), lets the user play with a category prop,
|
||||
then emits a fresh object after it's done. So what, what's the big deal? The important part to walk
|
||||
away from this dumb example is the things that are NOT in this sample component.
|
||||
away from this dumb example is the things that are NOT in this sample component.
|
||||
|
||||
This component doesn't save the data. This component doesn't make ajax calls, and this component
|
||||
doesn't mutate its props. What it does do is to allow the user to edit some object named "doodad"
|
||||
@@ -149,8 +153,8 @@ describe("A renderless component", () => {
|
||||
},
|
||||
});
|
||||
|
||||
// waits for "updated" Vue lifecycle hook to fire on the renderless
|
||||
// component. This is often good enough for waiting for
|
||||
// waits for "updated" Vue lifecycle hook to fire on the renderless
|
||||
// component. This is often good enough for waiting for
|
||||
// an initial ajax load to finish, for example
|
||||
await waitForLifecyleEvent(wrapper.vm, "updated");
|
||||
})
|
||||
@@ -161,4 +165,4 @@ describe("A renderless component", () => {
|
||||
// ...more tests
|
||||
})
|
||||
})
|
||||
```
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user