Skip to content

Instantly share code, notes, and snippets.

@tamaina
Last active March 14, 2022 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tamaina/43642876891ae3283987f59a195df3c9 to your computer and use it in GitHub Desktop.
Save tamaina/43642876891ae3283987f59a195df3c9 to your computer and use it in GitHub Desktop.
Misskey v12 Composition API 書き換え作業

この作業について

Options API(thisやdata()などを用いた記述)で書かれたVueコンポーネントをComposition API(setup()関数を用いた記述)に書き換えます。

簡便のため、<script setup>シュガー(=糖衣構文)を使用します。
詳しく知りたい方は<script setup>についてのvueのドキュメントを確認してください。

トップレベルで定義した変数や関数、importなどは、そのままtemplateタグ内で使用することができます。

ます、scriptタグにsetupを書き足し、もとのdefineComponentsはすべてコメントで囲います。 作業が終了した際は、コメントにした行は消去してください。

<template>...</template>

<script lang="ts" setup>
import { onMounted, ... } from 'vue';
import { defaultStore } from '@/store';

... // ここにコードを追加していく

/* (作業後削除する)
export default defineComponent({
	...
*/
</script>

<style>...</style>

<script>

もし書き換え前のファイルでexport default defineComponentの前に何らかの処理(コンポーネントを読み込んだ直後の1回きりしか実行しない処理)が書かれている場合、
次のように<script setup>の上に<script>タグを挿入し、その中に書いてください。

このとき、import行群はまとめて上のscriptの先頭に書きます。

<template>...</template>

<script lang="ts">
import { onMounted, ... } from 'vue';
import { defaultStore } from '@/store';

const hoge = /* nanka iroiro */

export default {
    hoge,
}
</script>

<script lang="ts" setup>
... // ここにコードを追加していく
</script>

<style>...</style>

1. props

// Options APIでのpropsの例
export default defineComponent({
	props: {
		hoge: {
			type: Array as PropType<{ foo: string; bar: string }[]>,
			required: true,
		},
		fuga: {
			type: String,
			required: false,
			default: 'DEFAULT'
		},
	},
	...

propsを書き換えます。

まず、ファイル全体でthis.<prop名>props.<prop名>に置き換えます。

default値がある場合

次のようなprops定義を、import行群の次に書き加えます。withDefaultsやdefinePropsは糖衣構文に定義されているのでimportは不要です。

const props = withDefaults(defineProps<{
	hoge: { foo: string; bar: string }[];
	fuga?: string;
}>(), {
	fuga: 'DEFAULT',
});

default値がない場合

definePropsのみを使用します。

const props = defineProps<{
	hoge: { foo: string; bar: string }[];
	fuga?: string;
}>();

FYI: https://v3.ja.vuejs.org/api/sfc-script-setup.html#%E5%9E%8B%E3%81%AE%E3%81%BF%E3%81%AE-props-emit-%E5%AE%A3%E8%A8%80

2. emit

<!-- Options APIでのemitsの例 -->
<template>
	<div @click="$emit('close')">
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
	emits: ['done', 'close'],

	methods: {
		hoge(result: any) {
			this.$emit('done', result);
			this.$emit('close');
		}
	},
});
</script>

ファイル全体でthis.$emit/$emitemitに置き換え、defineEmitsで以下の例のようにemitを定義します。
第2引数(v)は使用状況に応じて定義してみてください。

<template>
	<div @click="emit('close')">
</template>

<script lang="ts" setup>
const emit = defineEmits<{
	(e: 'done', v: any): void;
	(e: 'close'): void;
}>();

function hoge(v) {
	emit('done', v);
	emit('close')
}
</script>

See: https://v3.ja.vuejs.org/api/sfc-script-setup.html#%E5%9E%8B%E3%81%AE%E3%81%BF%E3%81%AE-props-emit-%E5%AE%A3%E8%A8%80

3. $el, $refs

<!-- Options APIでの$el, $refsの例 -->
<template>
<div class="root">
	<div ref="foo" />
	<XHoge ref="hoge" />
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import XHoge from '@/components/hoge.vue';

export default defineComponent({
	components: {
		XHoge
	},

	mounted() {
		console.log(this.$el); // HTMLDivElement
		console.log(this.$refs.foo); // HTMLDivElement
		console.log(this.$refs.hoge.fuga); // XHoge["fuga"]
	}
});
</script>

次のように書き換えます。後述する$refと同じ扱いになります。

<template>
<div ref="rootEl" class="root"> <!-- $elはrefにする -->
	<div ref="foo" />
	<XHoge ref="hoge" />
</div>
</template>

<script lang="ts" setup>
import { onMounted } from 'vue';
import XHoge from '@/components/hoge.vue';

let rootEl: HTMLDivElement = $ref();
let foo: HTMLDivElement = $ref();
let hoge: InstanceType<typeof XHoge> = $ref();

onMounted(() => { // ライフサイクルフックについては後述します
	console.log(rootEl); // HTMLDivElement | undefined
	console.log(foo); // HTMLDivElement | undefined
	console.log(hoge.fuga); // XHoge["fuga"] | undefined
});
</script>

FYI: https://v3.ja.vuejs.org/guide/composition-api-template-refs.html

  • ファイル全体でthis.$refs.<ref名><ref名>this.$elrootElと置き換えます。
  • 次に説明するdataなどと名前が被る可能性があるので、その場合は名前にElを付け足す(fooElなどにする)ようにしてください(template内のref指定も変更します)。
  • refはmount前に呼び出されてundefinedになる可能性があるで、そこを考慮してよしなに編集してください。

4. data(), 5. computed, 6. methods

// Options APIでのdata()の例
import { defineComponent } from 'vue';

export default defineComponent({
	data() {
		return {
			hogeList: [] as number[],
			staticObj: {
				foo: 'foo',
				bar: 'bar',
			},
		}
	},

	computed: {
		theFirst(): number | null {
			return this.hogeList[0] || null;
		},
	}

	methods: {
		pushToHoge(item: number) {
			this.hogeList.push(item);
		},
	},
});
  • Reactivity Transform(ref sugar)を使用します。
    • data()はletで定義します。リアクティブにしたい場合はref関数を使用します。
    • computedは$computed関数を使用します。
  • methodはfunctionとして定義します。
  • ファイル全体でそれぞれ次のように置き換えます。
    • this.<静的なdata名><静的なdata名>
    • this.<リアクティブなdata名><リアクティブなdata名>
    • this.<computed名><computed名>
    • this.<method名>(<method名>(
import { } from 'vue'; 

let hogeList: number[] = $ref([]);
const staticObj = {
	foo: 'foo',
	bar: 'bar',
};

const theFirst: number | null = $computed(() => hogeList[0] || null);

function pushToHoge(item: number) {
	hogeList.push(item);
}

7. ライフサイクルフック

See: https://v3.ja.vuejs.org/guide/composition-api-lifecycle-hooks.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF

// Options APIでのライフサイクルフックの例(mounted)
import { defineComponent } from 'vue';

export default defineComponent({
	mounted() {
		console.log('MOUNTED!');
	},

	beforeUnmount() {
		console.log('BEFORE_UNMOUNT!');
	},
});

次のように置き換えます。

// vueパッケージからライフサイクルフック関数をインポート
import { onMouted, onBeforeUnmount } from 'vue';

/* もろもろの処理 */

onMounted(() => {
	console.log('MOUNTED!');
});

onBeforeUnmount(() => {
	console.log('BEFORE_UNMOUNT!');
});

8. $watch

// Options APIでの$watch
this.$watch('hoge', () => console.log('change', 'hoge'));

this.$watchはwatch関数に置き換えます。
FYI: https://v3.ja.vuejs.org/api/computed-watch-api.html#watch

import { watch } from 'vue';

const props = defineProps<{ bar: string }>();
const foo = $ref(true);

watch(foo, () => console.log('change', 'foo'));
watch(() => props.bar, () => console.log('change', 'bar'));

9. expose

呼び出し先がデータや関数を使用している場合、外に公開するものをdefineExposeで明示する必要があります。

defineExpose({
	foo,
	hogeList,
});

FYI: https://v3.ja.vuejs.org/api/sfc-script-setup.html#defineexpose

10. slots, attrs

万が一slotsやattrsが必要になった場合には、それぞれuseSlotsuseAttrsヘルパーを使用します。
(コードが読みやすいようにpropsの前に定義するようにします。)

FYI: https://v3.ja.vuejs.org/api/sfc-script-setup.html#useslots-%E3%81%A8-useattrs

import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()

11. グローバルプロパティ

thisが使えなくなるため、グローバルプロパティ($i, $store, $instance, $t, $ts)も使えません。
グローバルプロパティはもともと保守性の低下を招くため、廃止する方針となっています。

各グローバルプロパティの置き換え方法を次に示します(<template>内に関しても編集してください)。

$i

@/accountから$iをインポートします。

import { $i } from '@/account';

$instance

@/instanceからインポートしたinstanceオブジェクトを使用します。
$instanceinstanceに置き換えます。

import { instance } from '@/instance';

$store, defaultStore

<!-- Options APIでの$store, defaultStoreの例 -->
<template>
	<div v-if="fuga">{{ $store.state.bar }}</div>
	<div v-if="$store.reactiveState.piyo.value">{{ $store.reactiveState.piyo.value }}</div>
	<XSwitch v-model="foo">SWITCH!</XSwitch>
</template>

<script lang="ts">
import { defaultStore } from '@/store';
import XSwitch from '@/components/switch';

export default defineComponent({
	components: {
		XSwitch,
	},

	computed: {
		foo: defaultStore.makeGetterSetter('foo'),
		fuga: defaultStore.makeGetterSetter('hoge', v => !v, v => !v),
	},
});
</script>

@/storeからインポートしたdefaultStoreを使用します。

通常はdefaultStore.reactiveStateから分割代入するのが簡単です。
fugaのようにmakeGetterSetterでカスタムのget/set関数を使う場合も例示しています。

defaultStore.stateを参照している場合、意図的に静的な値を参照することにした可能性があります(Issue)。
リアクティブが必要かどうか検討していただき、分割代入でリアクティブな値にするかそのまま$storedefaultStoreに置き換えるかを選択してください。

<template>
	<div v-if="fuga">{{ defaultStore.state.bar }}</div>
	<div v-if="piyo">{{ piyo }}</div>
	<XSwitch v-model="foo">SWITCH!</XSwitch>
</template>

<script lang="ts" setup>
import { defaultStore } from '@/store';
import XSwitch from '@/components/switch';

const { foo, piyo } = defaultStore.reactiveState;
const fuga = computed(defaultStore.makeGetterSetter('hoge', v => !v, v => !v));
</script>

$t, $ts

@/i18nからi18nをインポートし、$t/this.$ti18n.t$ts/this.$tsi18n.localeに置き換えます。

import { i18n } from '@/i18n';

console.log(i18n.t('_hoge.fuga', { foo: 30 }));
console.log(i18n.locale._foo.bar);

$route, $router

@/routerからrouterをインポートします。
$routerrouter$routerouter.currentRoute.valueに置き換えます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment