Vue3로 Firabase 로그인
개시하다
이 글은 2년 전에 쓴 것Vue vuex에서 Firebase 로그인 유지으로 Vue3+Composition API로 다시 썼다.구조도 바뀌었다.
자습서
시위 행진 여기 있다https://vue-firebase-demo-ebdef.web.app/
GiitHub에 소스 코드 업로드ErgoFriend/vue-firebase-auth
다음과 같은 요소를 결합하여 여러 개의 구성 요소가 상태를 공유하는 Vue 응용 프로그램을 제작한다.
vue-router
기반 변환개발 환경
응용 프로그램의 초기 형태를 만들기 위해 vue/cli를 설치합니다.
node v15.5.0
npm 7.3.0
npm install -g @vue/cli @vue/cli-service-global
# or
yarn global add @vue/cli @vue/cli-service-global
다음에 응용 프로그램의 모형을 만든다.vue create vue3-firebase-demo
Vue3 및 Type Script를 사용하여 설명서 선택을 선택합니다.Type Script/Router를 추가로 선택하고 결정하십시오.그런 다음 Vue2 또는 3을 사용하는지 묻습니다. 3을 선택하십시오.나머지는 오락대로만.결국 이렇게 될 것 같아서요.그러면 오락으로 모형을 만들어 보자.
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
npm ERR! code ERR_SOCKET_TIMEOUT
필자의 환경(win10+wsl2+ubuntu20)에서 상술한 오류가 발생하여 실행하였다
sudo vue create vue3-firebase-demo
.UI 만들기
UI를 구성하는 구성 요소를 만듭니다.
1. 머리글
머리글은 로그인 상태에 접근하는 구성 요소 중 하나입니다.
components에 파일 추가
src/components/LoginButton.vue
LoginButton.vue
<template>
<button type="button" class="google-button">
<span class="google-button__icon">
<svg viewBox="0 0 366 372" xmlns="http://www.w3.org/2000/svg">
<path
d="M125.9 10.2c40.2-13.9 85.3-13.6 125.3 1.1 22.2 8.2 42.5 21 59.9 37.1-5.8 6.3-12.1 12.2-18.1 18.3l-34.2 34.2c-11.3-10.8-25.1-19-40.1-23.6-17.6-5.3-36.6-6.1-54.6-2.2-21 4.5-40.5 15.5-55.6 30.9-12.2 12.3-21.4 27.5-27 43.9-20.3-15.8-40.6-31.5-61-47.3 21.5-43 60.1-76.9 105.4-92.4z"
id="Shape"
fill="#EA4335"
/>
<path
d="M20.6 102.4c20.3 15.8 40.6 31.5 61 47.3-8 23.3-8 49.2 0 72.4-20.3 15.8-40.6 31.6-60.9 47.3C1.9 232.7-3.8 189.6 4.4 149.2c3.3-16.2 8.7-32 16.2-46.8z"
id="Shape"
fill="#FBBC05"
/>
<path
d="M361.7 151.1c5.8 32.7 4.5 66.8-4.7 98.8-8.5 29.3-24.6 56.5-47.1 77.2l-59.1-45.9c19.5-13.1 33.3-34.3 37.2-57.5H186.6c.1-24.2.1-48.4.1-72.6h175z"
id="Shape"
fill="#4285F4"
/>
<path
d="M81.4 222.2c7.8 22.9 22.8 43.2 42.6 57.1 12.4 8.7 26.6 14.9 41.4 17.9 14.6 3 29.7 2.6 44.4.1 14.6-2.6 28.7-7.9 41-16.2l59.1 45.9c-21.3 19.7-48 33.1-76.2 39.6-31.2 7.1-64.2 7.3-95.2-1-24.6-6.5-47.7-18.2-67.6-34.1-20.9-16.6-38.3-38-50.4-62 20.3-15.7 40.6-31.5 60.9-47.3z"
fill="#34A853"
/>
</svg>
</span>
<span class="google-button__text">Sign in with Google</span>
</button>
</template>
<script lang='ts'>
import { defineComponent } from "vue";
export default defineComponent({
name: "LoginButton",
});
</script>
<style scoped>
.google-button {
height: 40px;
border-width: 0;
border-radius: 5px;
white-space: nowrap;
box-shadow: 1px 1px 0px 1px rgba(0, 0, 0, 0.05);
transition-property: background-color, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
padding: 0;
}
.google-button__icon {
display: inline-block;
vertical-align: middle;
margin: 8px 0 8px 8px;
width: 18px;
height: 18px;
box-sizing: border-box;
}
.google-button__icon--plus {
width: 27px;
}
.google-button__text {
display: inline-block;
vertical-align: middle;
padding: 0 24px;
font-size: 14px;
font-weight: bold;
font-family: "Roboto", arial, sans-serif;
}
</style>
src/components/User.vueUser.vue
<template>
<div class="user" v-if="true">
<img class="photoURL" src="" alt="" />
<h3 class="displayName">Guest</h3>
<button
type="button"
class="button is-small is-info is-outlined"
>
Sign out
</button>
</div>
<div class="user" v-else>
<LoginButton />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import LoginButton from "@/components/LoginButton.vue";
export default defineComponent({
name: "User",
components: {
LoginButton,
},
});
</script>
<style scoped>
.user {
margin-left: auto;
height: 50px;
display: inline-flex;
align-items: center;
flex-wrap: wrap;
}
.displayName,
button {
white-space: nowrap;
}
.displayName {
margin: 0 20px 0 10px;
}
button {
font-weight: 600;
}
.photoURL {
border-radius: 50%;
object-fit: cover;
width: 50px;
height: 50px;
}
</style>
src/components/Header.vueHeader.vue
<template>
<div class="header">
<router-link to="/">Home</router-link>
<p>/</p>
<router-link to="/setting">Setting</router-link>
<User />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import User from "@/components/User.vue";
export default defineComponent({
name: "Header",
components: {
User,
},
});
</script>
<style scoped>
.header {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 10px 0;
}
p {
margin: 0 5px;
}
</style>
응용 프로그램에 머리글 추가
이 응용 프로그램은 Water.css를 사용하기 위해
App.vue
로 읽습니다.src/App.vue
App.vue
<template>
<Header />
<router-view />
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Header from "@/components/Header.vue";
export default defineComponent({
name: "App",
components: {
Header,
},
});
</script>
<style>
@import "https://cdn.jsdelivr.net/npm/water.css@2/out/dark.min.css";
</style>
2. 홈 화면
UI를 준비하여 응용 프로그램에서 처음 표시되는 화면에 로그인한 사람의 이름을 표시합니다.
src/views/Home.vue
Home.vue
<template>
<div class="hello">
<h1>Hello, {{ guest }}</h1>
<p>
This is a example of an article.
</p>
<p>
Vue3 + Composition API + store pattern + Firebase Auth
</p>
<h3>Related links</h3>
<ul>
<li><a href="https://github.com/ErgoFriend/vue-firebase-authl" target="_blank" rel="noopener">GitHub Repo</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">Zenn Article</a></li>
</ul>
<br>
<p>Copyright © 2021 <a href="https://kasu.dev">kasu.dev</a> All Rights Reserved.</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Home',
setup(){
const guest = '{{ Guest }}'
return { guest }
}
});
</script>
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
3. 화면 설정
로그인자의 사용자 이름과 프로필 이미지를 변경할 페이지를 준비합니다.
이 프로그램으로 로그인 상태에 접근하는 구성 요소는 머리, 홈 화면, 설정 화면 3개입니다.
src/views/Setting.vue
Setting.vue
<template>
<div class="view">
<h1>Setting</h1>
<div class="updater">
<label>displayName</label>
<input type="text" v-model="displayName" />
</div>
<div class="updater">
<label>photoURL</label>
<input type="text" v-model="photoURL" />
</div>
<button>Update</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from "vue";
export default defineComponent({
name: "Setting",
setup() {
const displayName = ref("");
const photoURL = ref("");
return { displayName, photoURL };
},
});
</script>
<style scoped>
.updater {
display: flex;
flex-wrap: wrap;
align-items: center;
}
label {
flex: 1;
}
input {
flex: 3;
}
button {
float: right;
}
</style>
4. 라우팅
홈 화면과 설정 화면을 이동할 수 있도록
vue-router
설정합니다.src/router/index.ts
index.ts
import { createWebHistory, createRouter } from "vue-router";
import Home from "@/views/Home.vue";
import Setting from "@/views/Setting.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/setting",
name: "Setting",
component: Setting,
}
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
상점 만들기
로그인 상태를 관리하는 스토어
관리 로그인 상태를 만듭니다
authStore
.로그인 상태를 유지하는 구성 요소에 등록
authStore
, 로그인 상태를 이용한 구성 요소에서 호출useAuthStore()
, 로그인된authStore에서 데이터를 읽고 호출하는 방법.src/stores/auth.ts
auth.ts
import { inject, InjectionKey, reactive } from "vue";
type DummyUser = {
displayName: string;
photoURL: string;
}
const dummyUser: DummyUser = {
displayName: 'test',
photoURL: 'https://pbs.twimg.com/profile_images/1308010958862905345/-SGZioPb_400x400.jpg'
}
const authStore = () => {
console.log('init authStore')
const state = reactive({ isLoggedin: false, displayName: '', photoURL: ''})
const setUser = (user: DummyUser | null) => {
state.isLoggedin = !!user
if (user) {
state.displayName = user.displayName ?? ''
state.photoURL = user.photoURL ?? ''
}
}
const signin = () => {
setUser(dummyUser)
}
const signout = () => setUser(null)
const updateUser = (input: DummyUser) => {
setUser(input)
}
return {
state,
setUser,
signin,
signout,
updateUser,
};
}
export default authStore
export type AuthStore = ReturnType<typeof authStore>;
export const authStoreKey: InjectionKey<AuthStore> = Symbol('authStore');
export const useAuthStore = () => {
const store = inject(authStoreKey);
if (!store) {
throw new Error(`${authStoreKey} is not provided`);
}
return store;
}
스토어 연결
상점 모드에서 상점을 사용할 구성 요소에 연결합니다.이번에는 전체 프로그램에서 로그인 상태를 사용하기 위해 루트
App
로provide를 진행 중입니다.src/main.ts
main.ts
import { createApp } from 'vue'
import App from '@/App.vue'
import router from '@/router'
import authStore, { authStoreKey } from '@/stores/auth'
const app = createApp(App)
app.use(router)
app.provide(authStoreKey, authStore())
app.mount('#app')
상점의 호출
각 구성 요소에서 로그인 상태에 액세스합니다.
매장에서는 authStore inject 후
useAuthStore()
를 다음과 같이 사용합니다.사용 예
const { state } = useAuthStore();
console.log('name:', state.displayName)
src/stores/auth.tsexport const useAuthStore = () => {
const store = inject(authStoreKey);
if (!store) {
throw new Error(`${authStoreKey} is not provided`);
}
return store;
}
1. 머리글
LoginButton.vue
로그인 단추를 누르면authStore의 로그인 함수
signin()
를 실행합니다.src/components/LoginButton.vue
LoginButton.vue
<template>
<button type="button" class="google-button" @click="signin()">
<span class="google-button__icon">
<svg viewBox="0 0 366 372" xmlns="http://www.w3.org/2000/svg">
<path
d="M125.9 10.2c40.2-13.9 85.3-13.6 125.3 1.1 22.2 8.2 42.5 21 59.9 37.1-5.8 6.3-12.1 12.2-18.1 18.3l-34.2 34.2c-11.3-10.8-25.1-19-40.1-23.6-17.6-5.3-36.6-6.1-54.6-2.2-21 4.5-40.5 15.5-55.6 30.9-12.2 12.3-21.4 27.5-27 43.9-20.3-15.8-40.6-31.5-61-47.3 21.5-43 60.1-76.9 105.4-92.4z"
id="Shape"
fill="#EA4335"
/>
<path
d="M20.6 102.4c20.3 15.8 40.6 31.5 61 47.3-8 23.3-8 49.2 0 72.4-20.3 15.8-40.6 31.6-60.9 47.3C1.9 232.7-3.8 189.6 4.4 149.2c3.3-16.2 8.7-32 16.2-46.8z"
id="Shape"
fill="#FBBC05"
/>
<path
d="M361.7 151.1c5.8 32.7 4.5 66.8-4.7 98.8-8.5 29.3-24.6 56.5-47.1 77.2l-59.1-45.9c19.5-13.1 33.3-34.3 37.2-57.5H186.6c.1-24.2.1-48.4.1-72.6h175z"
id="Shape"
fill="#4285F4"
/>
<path
d="M81.4 222.2c7.8 22.9 22.8 43.2 42.6 57.1 12.4 8.7 26.6 14.9 41.4 17.9 14.6 3 29.7 2.6 44.4.1 14.6-2.6 28.7-7.9 41-16.2l59.1 45.9c-21.3 19.7-48 33.1-76.2 39.6-31.2 7.1-64.2 7.3-95.2-1-24.6-6.5-47.7-18.2-67.6-34.1-20.9-16.6-38.3-38-50.4-62 20.3-15.7 40.6-31.5 60.9-47.3z"
fill="#34A853"
/>
</svg>
</span>
<span class="google-button__text">Sign in with Google</span>
</button>
</template>
<script lang='ts'>
import { defineComponent } from "vue";
import { useAuthStore } from "@/stores/auth";
export default defineComponent({
name: "LoginButton",
setup() {
const { signin } = useAuthStore();
return { signin };
},
});
</script>
<style scoped>
.google-button {
height: 40px;
border-width: 0;
border-radius: 5px;
white-space: nowrap;
box-shadow: 1px 1px 0px 1px rgba(0, 0, 0, 0.05);
transition-property: background-color, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
padding: 0;
}
.google-button__icon {
display: inline-block;
vertical-align: middle;
margin: 8px 0 8px 8px;
width: 18px;
height: 18px;
box-sizing: border-box;
}
.google-button__icon--plus {
width: 27px;
}
.google-button__text {
display: inline-block;
vertical-align: middle;
padding: 0 24px;
font-size: 14px;
font-weight: bold;
font-family: "Roboto", arial, sans-serif;
}
</style>
User.vue로그인할 때 개인 프로필 이미지와 이름을 표시합니다. 그렇지 않으면 로그인 단추를 표시합니다.
src/components/User.vue
User.vue
<template>
<div class="user" v-if="state.isLoggedin">
<img class="photoURL" :src="state.photoURL" alt="" />
<h3 class="displayName">{{ state.displayName }}</h3>
<button
type="button"
class="button is-small is-info is-outlined"
@click="signout()"
>
Sign out
</button>
</div>
<div class="user" v-else>
<LoginButton />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useAuthStore } from "@/stores/auth";
import LoginButton from "@/components/LoginButton.vue";
export default defineComponent({
name: "User",
components: {
LoginButton,
},
setup() {
const { signout, state } = useAuthStore();
return {
state,
signout,
};
},
});
</script>
<style scoped>
.user {
margin-left: auto;
height: 50px;
display: inline-flex;
align-items: center;
flex-wrap: wrap;
}
.displayName,
button {
white-space: nowrap;
}
.displayName {
margin: 0 20px 0 10px;
}
button {
font-weight: 600;
}
.photoURL {
border-radius: 50%;
object-fit: cover;
width: 50px;
height: 50px;
}
</style>
2. 홈 화면
로그인자의 이름을 표시합니다.
src/views/Home.vue
Home.vue
<template>
<div class="hello">
<h1>Hello, {{ state.isLoggedin ? state.displayName : guest }}</h1>
<p>
This is a example of an article.
</p>
<p>
Vue3 + Composition API + store pattern + Firebase Auth
</p>
<h3>Related links</h3>
<ul>
<li><a href="https://github.com/ErgoFriend/vue-firebase-authl" target="_blank" rel="noopener">GitHub Repo</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">Zenn Article</a></li>
</ul>
<br>
<p>Copyright © 2021 <a href="https://kasu.dev">kasu.dev</a> All Rights Reserved.</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useAuthStore } from '@/stores/auth';
export default defineComponent({
name: 'Home',
setup(){
const guest = '{{ Guest }}'
const { state } = useAuthStore()
return { guest, state }
}
});
</script>
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
3. 화면 설정
로그인자의 이름과 이미지 URL을 변경할 수 있습니다.
우선 빈 문자열로 초기값을 입력하는 v-model을 준비합니다.이후
watchEffect
상점을 감시하여 값을 동기화합니다.src/views/Setting.vue
Setting.vue
<template>
<div class="view">
<h1>Setting</h1>
<div class="updater">
<label>displayName</label>
<input type="text" v-model="displayName" />
</div>
<div class="updater">
<label>photoURL</label>
<input type="text" v-model="photoURL" />
</div>
<button @click="update()">Update</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from "vue";
import { useAuthStore } from "@/stores/auth";
export default defineComponent({
name: "Setting",
setup() {
const { state, updateUser } = useAuthStore();
const displayName = ref("");
const photoURL = ref("");
const update = () =>
updateUser({ displayName: displayName.value, photoURL: photoURL.value });
watchEffect(() => {
displayName.value = state.displayName;
photoURL.value = state.photoURL;
});
return { displayName, photoURL, update };
},
});
</script>
<style scoped>
.updater {
display: flex;
flex-wrap: wrap;
align-items: center;
}
label {
flex: 1;
}
input {
flex: 3;
}
button {
float: right;
}
</style>
Firebase 추가
응용 프로그램에 Firebase 패키지를 추가합니다.
npm install --save firebase
or
yarn add firebase
설정
Firebase를 응용 프로그램에 연결합니다.
main.ts
Firebase 설정이 진행 중입니다.config
Firebase의 프로젝트 설정 화면에 웹을 추가한 제 응용 프로그램에서 얻습니다.https://console.firebase.google.com/ src/main.ts
main.ts
import { createApp } from 'vue'
import firebase from "firebase/app"
import 'firebase/auth'
import App from '@/App.vue'
import router from '@/router'
import authStore, { authStoreKey } from '@/stores/auth'
const config = {
apiKey: "AIzaSyC65X-Nz7uVsrPR1x6rI2tx_Mjdny6pAl8",
authDomain: "vue-firebase-demo-ebdef.firebaseapp.com",
projectId: "vue-firebase-demo-ebdef",
storageBucket: "vue-firebase-demo-ebdef.appspot.com",
messagingSenderId: "213709504759",
appId: "1:213709504759:web:40aac8e6d6cc38cbeb53f2",
measurementId: "G-EVD8ZEB5M3"
}
firebase.initializeApp(config);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
const app = createApp(App)
app.use(router)
app.provide(authStoreKey, authStore())
app.mount('#app')
응용 프로그램 가져오기
siginin()
·siginin()
에서 실행firebase.auth()
, 로그인 상태가 로그인·로그아웃을 이용할 때 호칭firebase.auth().onAuthStateChanged
에서 setUser()
state를 변경합니다.src/stores/auth.ts
auth.ts
import firebase from "firebase/app";
import 'firebase/auth';
import { inject, InjectionKey, reactive } from "vue";
const authStore = () => {
console.log('init authStore')
const state = reactive({ isLoggedin: false, displayName: '', photoURL: ''})
const setUser = (user: firebase.User | null) => {
state.isLoggedin = !!user
if (user) {
state.displayName = user.displayName ?? ''
state.photoURL = user.photoURL ?? ''
}
}
const signin = () => {
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
}
const signout = () => firebase.auth().signOut()
const updateUser = (input: { displayName?: string; photoURL?: string }) => {
firebase.auth().currentUser?.updateProfile(input)
.then(() => setUser(firebase.auth().currentUser)
)
}
firebase.auth().onAuthStateChanged((user) => setUser(user))
return {
state,
setUser,
signin,
signout,
updateUser,
};
}
export default authStore
export type AuthStore = ReturnType<typeof authStore>;
export const authStoreKey: InjectionKey<AuthStore> = Symbol('authStore');
export const useAuthStore = () => {
const store = inject(authStoreKey);
if (!store) {
throw new Error(`${authStoreKey} is not provided`);
}
return store;
}
이 응용 프로그램은 여기까지만 하면 완성된다.이번에는 상점 모드로 로그인 상태를 관리하는 앱을 만들었다.데모로 이동하지 않을 경우 GiitHub 웨어하우스를 참조하거나 Discussion으로 보내십시오.끝말
끝까지 읽어주셔서 감사합니다.
필자의 최초의 프로그래밍 경험은 Vue였고, 그 후에 바로 React를 하기 시작했지만, 나는 당시에 매우 고생했던 것을 기억한다.Composition API 시대의 Vue에서 React를 시작하면 비교적 가벼운 느낌도 든다.
Reference
이 문제에 관하여(Vue3로 Firabase 로그인), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/kasu/articles/70a77bd2d1ee01텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)