[Vue入門] リストレンダリング

v-for

配列に基づいて項目のリストをレンダリングするには、v-for ディレクティブを使用します。v-for ディレクティブでは、item in items という形式の特別な構文が必要になります。ここで、items は元のデータの配列を指し、item は反復処理の対象となっている配列要素のエイリアスを指します:

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
  {{ item.message }}
</li>

v-for のスコープ内では、テンプレート内の式から親スコープのすべてのプロパティにアクセスできます。さらに、v-for では以下のように現在の項目のインデックスを指す、2 つ目の省略可能なエイリアスもサポートされています:

const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

Parent – 0 – FooParent – 1 – Bar

プレイグラウンドで試す

v-for の変数のスコープは、次の JavaScript と同様です:

const parentMessage = 'Parent'
const items = [
  /* ... */
]

items.forEach((item, index) => {
  // ここからスコープの外の `parentMessage` にはアクセスできますが、
  // `item` と `index` はこの中でしか使用できません。
  console.log(parentMessage, item.message, index)
})

v-for の値が forEach のコールバック関数のシグネチャと一致している様子に注目してください。実際、関数の引数で分割代入を使用するときと同様に、v-for の item のエイリアスでも分割代入を使用することができます:

<li v-for="{ message } in items">
  {{ message }}
</li>

<!-- index のエイリアスを伴う場合 -->
<li v-for="({ message }, index) in items">
  {{ message }} {{ index }}
</li>

ネストされた v-for でも、スコープの挙動はネストされた関数と同様です。以下のように、それぞれの v-for のスコープでは親のスコープにアクセスできます:

<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>

区切り文字として in の代わりに of を使用して、JavaScript のイテレーター構文に近付けることもできます:

<div v-for="item of items"></div>

v-for をオブジェクトに適用する

v-for は、オブジェクトの各プロパティを反復処理するのにも使用できます。

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

以下のように 2 つ目のエイリアスを指定すると、プロパティの名前 (「キー」とも呼ばれる) を取り出すことができます:

<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

さらに 3 つ目のエイリアスを追加すると、インデックスを取り出せます:

<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

プレイグラウンドで試す

注意

オブジェクトを反復処理する際の順序は Object.keys() による列挙に基づきますが、JavaScript エンジンの異なる実装間での一貫性は保証されていません。

v-for で範囲を使用する

v-for は、整数を取ることもできます。その場合、1...n のような範囲に従って、その回数だけテンプレートが繰り返されます。

<span v-for="n in 10">{{ n }}</span>

n の値は 0 ではなく 1 から始まることに注意してください。

<template> に v-for を適用する

テンプレートに v-if を適用する場合と同様に、 <template> タグに v-for を適用すると、複数の要素からなるブロックをレンダリングできます。例:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for と v-if を組み合わせる場合

注意

暗黙の優先順位があるため、v-if と v-for を同一の要素に対して使用することは推奨されません。詳しくはスタイルガイドを参照してください。

同じノードに両方が存在する場合、v-for よりも v-if のほうが優先順位が高くなります。これは、以下のように v-for のスコープにある変数には v-if の条件式からアクセスできないことを意味します:

<!--
"todo" というプロパティがインスタンスで未定義となるため、
エラーがスローされます。
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

この問題は、以下のようにラップ用の <template> タグを設けて、そこに v-for を移動することで解決できます (このほうがより明示的でもあります):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

key による状態管理

v-for でレンダリングされた要素のリストを Vue が更新するとき、デフォルトでは「その場での修繕」(in-place patch) という戦略が用いられます。データ項目の順序が変更された場合、Vue は項目の順序に合うように DOM 要素を移動させるのではなく、個々の要素をその位置のままで修正し、各インデックスでレンダリングされるべきものを反映させます。

このデフォルトのモードは効率性が高いものの、これが適すのは、リストのレンダリング出力が子コンポーネントの状態や一時的な DOM の状態 (フォームの入力値など) に依存しない場合に限られます

Vue に各ノードを一意に追跡するためのヒントを与え、既存の要素を再利用して並べ替えを適用できるようにするには、以下のように各項目に一意の key 属性を指定する必要があります:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

<template v-for> を例に取ると、key は以下のように <template> の中に置きます:

<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template>

注意

ここでいう key は、v-bind でバインドされる特別な属性です。v-for をオブジェクトに適用するときのプロパティのキーの変数と混同しないように注意してください。

v-for の key 属性は、可能な場合は必ず指定することが推奨されます。ただし、反復処理する DOM の内容が単純なものである (つまりコンポーネントやステートフルな DOM 要素を含まない) 場合、またはパフォーマンス向上のために意図的にデフォルト動作を用いたい場合は、この限りではありません。

key のバインディングにはプリミティブ型の値、つまり文字列と数値が想定されます。v-for の key にオブジェクトを指定してはいけません。key 属性の詳しい使い方については、key API のドキュメントを参照してください。

v-for をコンポーネントに適用する#

このセクションは、コンポーネントについての知識があることを前提としています。読み飛ばして、後で戻ってくるのも大丈夫です。

通常の要素と同様に、コンポーネントにも v-for を直接適用することができます (key を指定するのを忘れないでください):

<my-component v-for="item in items" :key="item.id"></my-component>

ただし、これだけではデータが自動的にコンポーネントに渡されるようにはなりません。なぜなら、コンポーネントはそれ自身の独立したスコープを持つからです。コンポーネントに反復処理対象のデータを渡すには、以下のようにプロパティを併用する必要があります:

<my-component
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
></my-component>

item が自動的に注入されないようになっている理由は、そうしてしまうと、コンポーネントが v-for の動作と密に結合してしまうためです。データの供給源を明示的に指定することにより、コンポーネントが別の場面でも再利用できるような作りになっています。

シンプルな ToDo リストのサンプルで、v-for でコンポーネントのリストをレンダリングするとき、各インスタンスに異なるデータをどのように渡せばよいかを確認できます。

配列の変更の検出

ミューテーションメソッド

Vue は監視対象の配列のミューテーションメソッドをラップして、これらのメソッドでビューの更新が一緒にトリガーされるようにしています。Vue がラップしているメソッドは次の通りです:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

配列の置き換え

ミューテーションメソッドはその名が示す通り、呼び出し元の配列を変化させるメソッドです。これに対し、filter()concat()slice() など、呼び出し元の配列を変化させないメソッドもあります。これらのメソッドは常に新しい配列を返します。ミューテーションしないメソッドを扱う場合は、以下のように、古い配列を新しい配列に置き換える必要があります:

// `item` は配列値の参照です
items.value = items.value.filter((item) => item.message.match(/Foo/))

このようにすると、Vue が既存の DOM を破棄してリスト全体を再レンダリングするように思えるかもしれませんが、幸いにもそのようなことはありません。Vue には DOM 要素を最大限に再利用するためのスマートな発見的アルゴリズムが実装されているため、既存の配列を、重複するオブジェクトが含まれる新しい配列に置き換える場合でも、非常に効率的な処理が行われます。

フィルタリング/並べ替えの結果を表示する

時には、配列の元のデータを実際に変更することやリセットすることなしに、フィルタリングや並べ替えを適用したバージョンを表示したいことがあります。そのような場合には、フィルタリングや並べ替えを適用した配列を返す算出プロパティを作成することができます。

例:

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})
<li v-for="n in evenNumbers">{{ n }}</li>

算出プロパティが使えない場所 (例えばネストされた v-for ループの内側) では、以下のようにメソッドを使用できます:

const sets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

function even(numbers) {
  return numbers.filter((number) => number % 2 === 0)
}
<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

算出プロパティの中で reverse() と sort() を使用するときは注意してください!これら 2 つのメソッドには、算出プロパティのゲッターの中では避けるべき、元の配列を変更するという作用があります。以下のように、これらのメソッドを呼び出す前には元の配列のコピーを作成します:

- return numbers.reverse()
+ return [...numbers].reverse()

[Vue入門] 条件付きレンダリング

v-if

v-if ディレクティブは、ブロックを条件に応じてレンダリングしたい場合に使用されます。ブロックは、ディレクティブの式が真を返す場合のみレンダリングされます。

<h1 v-if="awesome">Vue is awesome!</h1>

v-else

v-if に対して “else block” を示すために、v-else ディレクティブを使用できます:

<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

Toggle

Vue is awesome!

Try it in the Playground

v-else 要素は、v-if または v-else-if 要素の直後になければなりません。それ以外の場合は認識されません。

v-else-if

v-else-if は、名前が示唆するように、v-if の “else if block” として機能します。また、複数回連結することもできます:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-else と同様に、v-else-if 要素は v-if 要素または v-else-if 要素の直後になければなりません。

<template> での v-if

v-if はディレクティブなので、単一の要素に付加する必要があります。しかし、1 要素よりも多くの要素と切り替えたい場合はどうでしょうか?このケースでは、非表示ラッパー (wrapper) として提供される、<template> 要素で v-if を使用できます。最終的にレンダリングされる結果は、<template> 要素は含まれません。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-else と v-else-if は <template> でも使用可能です。

v-show

条件的に要素を表示するための別のオプションは v-show です。使用方法はほとんど同じです:

<h1 v-show="ok">Hello!</h1>

違いは v-show による要素は常レンダリングされて DOM に残るということです。v-show はシンプルに要素の display CSS プロパティを切り替えます。

v-show は <template> 要素をサポートせず、v-else とも連動しないということに注意してください。

v-if vs v-show

v-if は、イベントリスナと子コンポーネント内部の条件ブロックが適切に破棄され、そして切り替えられるまでの間再作成されるため、”リアル”な条件レンダリングです。

v-if はまた 遅延レンダリング (lazy) でもあります。 初期表示において状態が false の場合、何もしません。つまり条件付きブロックは、条件が最初に true になるまでレンダリングされません。

一方で、v-show はとてもシンプルです。要素は初期条件に関わらず常にレンダリングされ、シンプルな CSS ベースの切り替えによって表示されます。

一般的に、v-if はより高い切り替えコストを持っているのに対して、 v-show はより高い初期レンダリングコストを持っています。 そのため、とても頻繁に何かを切り替える必要があれば v-show を選び、条件が実行時に変更することがほとんどない場合は、v-if を選びます。

v-if と v-for

Note

暗黙的な優先順位により、 v-if と v-for を同じ要素で利用することは 推奨されません。 詳細については スタイルガイド を参照ください。

v-if と v-for が同じ要素に両方つかわれる場合、 v-if が先に評価されます。

[Vue入門] クラスとスタイルのバインディング

データバインディングは、HTML 要素に持たせる CSS クラスのリストやインラインのスタイルを自在に操作したいという、よくあるニーズに応えます。どちらも属性なので v-bind で扱うことができ、あとは式で最終的な文字列を算出すればよいだけです。しかし、文字列の結合に手を出すのは、手間がかかり、間違いが起きやすくなるものです。そこで、Vue では class や style に対して v-bind を用いるとき、特別な拡張が利用できるようになっています。文字列のほかに、オブジェクトまたは配列として評価される式も利用できます。

HTML クラスのバインディング

オブジェクトへのバインディング

:class (v-bind:class の省略記法) では、オブジェクトを渡して CSS クラスを動的に切り替えることができます:

<div :class="{ active: isActive }"></div>

上の構文は、コンポーネントのデータの isActive というプロパティが真値(true)であるかどうかによって active という CSS クラスを含めるかどうかを決定する、という意味になります。

オブジェクトのフィールドを増やせば、複数のクラスをトグルすることができます。さらに、:class ディレクティブは通常の class 属性と共存させることもできます。例えば、次のような状態があるとします:

const isActive = ref(true)
const hasError = ref(false)

そしてテンプレートが次のようになっているとします:

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>

このとき、レンダリング結果は次のようになります:

<div class="static active"></div>

isActive や hasError が変化すると、それに合わせてクラスのリストも更新されます。例えば、hasError が true になればクラスのリストは "static active text-danger" に変わります。

バインドするオブジェクトはインラインにしなくても構いません:

const classObject = reactive({
  active: true,
  'text-danger': false
})
<div :class="classObject"></div>

これも、同じレンダリング結果を得られます。オブジェクトを返す算出プロパティにクラスをバインドすることも可能です。次の例は、よく使われる強力なパターンです:

const isActive = ref(true)
const error = ref(null)

const classObject = computed(() => ({
  active: isActive.value && !error.value,
  'text-danger': error.value && error.value.type === 'fatal'
}))
<div :class="classObject"></div>

配列へのバインディング

次のように :class を配列にバインドすると、クラスのリストを適用することができます:

const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>

レンダリング結果は次のようになります:

<div class="active text-danger"></div>

リストに含まれる特定のクラスを条件に基づいて切り替えたい場合には、三項演算子を使えば実現できます:

<div :class="[isActive ? activeClass : '', errorClass]"></div>

この場合、errorClass は常に適用され、activeClass は isActive が真のときだけ適用されます。

しかし、条件を付けたいクラスが複数あると、これでは少し冗長になります。そこで、配列構文の中でオブジェクト構文を使うこともできるようになっています:

<div :class="[{ active: isActive }, errorClass]"></div>

コンポーネントでの使用

このセクションは、コンポーネントについての知識があることを前提としています。スキップして、後から読み直すのでも大丈夫です。

ルート要素が 1 つだけのコンポーネントで class 属性を使用すると、そこで指定したクラスがコンポーネントのルート要素に追加され、すでに指定されている既存のクラスとマージされます。

例えば、my-component という名前のコンポーネントがあり、次のようなテンプレートになっているとします:

<!-- 子コンポーネントのテンプレート -->
<p class="foo bar">Hi!</p>

そして、コンポーネントを使う際にクラスをいくつか追加します:

<!-- コンポーネントを使用する時点 -->
<my-component class="baz boo"></my-component>

レンダリングされる HTML は次のようになります:

<p class="foo bar baz boo">Hi</p>

クラスバインディングでも同様です:

<my-component :class="{ active: isActive }"></my-component>

isActive が真値のとき、レンダリングされる HTML は次のようになります:

<p class="foo bar active">Hi</p>

コンポーネントに複数のルート要素を持たせているときは、どの要素にクラスを渡すか指定する必要があります。これは、以下のように $attrs コンポーネントプロパティを使って行います:

<!-- $attrs を使った my-component のテンプレート -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
<my-component class="baz"></my-component>

レンダリング結果は次のようになります:

<p class="baz">Hi!</p>
<span>This is a child component</span>

コンポーネントの属性の継承については、フォールスルー属性のセクションで詳しく説明しています。

インラインスタイルのバインディング

オブジェクトへのバインディング

:style では次のような JavaScript のオブジェクト値へのバインディングがサポートされ、HTML 要素の style プロパティ に対応します:

const activeColor = ref('red')
const fontSize = ref(30)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

CSS プロパティのキーにはキャメルケース (camelCase) が推奨されますが、:style では CSS の実際の書き方に対応するケバブケース (kebab-cased) のキーもサポートされています。例:

<div :style="{ 'font-size': fontSize + 'px' }"></div>

テンプレートをすっきりさせるため、多くの場合、次のようにスタイルオブジェクトを直接バインドするとよいでしょう:

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})
<div :style="styleObject"></div>

スタイルへのオブジェクトのバインディングも、オブジェクトを返す算出プロパティと組み合わせて使用することが多くあります。

配列へのバインディング

:style は、複数のスタイルオブジェクトからなる配列にバインドすることができます。各オブジェクトはマージされ、同じ要素に適用されます:

<div :style="[baseStyles, overridingStyles]"></div>

自動プレフィックス

:style で ベンダープレフィックスを必要とする CSS プロパティを指定すると、Vue が適切なプレフィックスを自動的に追加します。Vue は、実行時にブラウザーでどのスタイルプロパティがサポートされているかをチェックして、適切なものを追加します。特定のプロパティがブラウザーでサポートされていない場合、Vue はさまざまなプレフィックスのバリエーションをテストし、サポートされているものを見つけようと試みます。

複数の値

style プロパティには、プレフィックス付きを含む複数の値を、配列で指定することができます。例:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

このように指定すると、配列に含まれる値のうち、ブラウザーでサポートされる最後の値のみがレンダリングに使われます。この例では、接頭辞なしのバージョンのフレックスボックスをサポートするブラウザーでは、display: flex がレンダリングに使われます。

[Vue入門]Computed Properties

基本的な例

テンプレート内に式を書けるのはとても便利ですが、非常に簡単な操作しかできません。テンプレート内に多くのロジックを詰め込むと、コードが肥大化し、メンテナンスが難しくなります。例えば、配列が入れ子(ネスト)になっているオブジェクトがあった場合:

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

そして、author がすでにいくつかの books を持っているかどうかによって、異なるメッセージを表示したいとします:

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

この時点で、テンプレートが少しごちゃごちゃしてきました。しばらく眺めて、やっとこれが author.books に依存した計算をしていることに気づくでしょう。さらに重要なことは、同じ計算をテンプレートの中で複数回使う場合、おそらく繰り返して使いたくはないでしょう。

上記の理由から、リアクティブなデータを含む複雑なロジックには算出プロパティを使用すべきです。以下は上記と同じ例をリファクタリングしたものです:

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 算出プロパティの参照
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

プレイグラウンドで試す

ここでは、publishedBooksMessage という算出プロパティを宣言しています。computed() 関数は getter 関数が渡されることを想定しており、返り値は 算出された ref となります。通常の ref と同様に、publishedBooksMessage.value で算出結果を参照することができます。また、算出結果はテンプレート内では自動的にアンラップされるため、テンプレート内では .value なしで参照することができます。

算出プロパティは、自動的にリアクティブな依存関係を追跡します。Vue は publishedBooksMessage の算出が author.books に依存することを知っているので、author.books が変わると publishedBooksMessage に依存する全てのバインディングを更新します。

算出プロパティ vs メソッド

こういった式を持つメソッドを呼び出すことで、同じ結果が実現できることに気付いたかもしれません:

<p>{{ calculateBooksMessage() }}</p>
// コンポーネント内
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

算出プロパティの代わりに、同じような関数をメソッドとして定義することもできます。最終的には、2 つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるという違いがあります。算出プロパティは、リアクティブな依存関係が更新されたときにだけ再評価されます。これはつまり、 author.books が変わらない限りは、publishedBooksMessage に何度アクセスしても、getter 関数を再び実行することなく、以前計算された結果を即時に返すということです。

Date.now() はリアクティブな依存ではないため、次の算出プロパティは二度と更新されないことを意味します:

const now = computed(() => Date.now())

対称的に、メソッド呼び出しは、再描画が起きると常に関数を実行します。

なぜキャッシングが必要なのでしょうか?巨大な配列をループしたり多くの計算を必要とする、コストの高い list という算出プロパティがあることを想像してみてください。list に依存する他の算出プロパティもあるかもしれません。その場合、キャッシングがなければ必要以上に list の getter を実行することになってしまいます。キャッシングしたくない場合は、代わりにメソッドを使いましょう。

書き込み可能な 算出関数

算出プロパティは、デフォルトでは getter 関数のみです。算出プロパティに新しい値を代入しようとすると、ランタイム警告が表示されます。まれに「書き込み可能な」算出プロパティが必要な場合があります。その場合は getter 関数と setter 関数の両方を提供することで、それを作成することができます:

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter 関数
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter 関数
  set(newValue) {
    // 注意: ここでは、破壊的な代入構文を使用しています。
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

fullName = 'John Doe' を呼ぶと、setter 関数が呼び出され、firstName と lastName が適切に更新されます。

ベストプラクティス

getter 関数は副作用のないものでなければならない

算出プロパティにおける getter 関数は計算のみを行い、副作用がないようにすることが重要です。例えば、非同期リクエストや、DOM を変化させないようにしましょう!算出プロパティは他の値に基づいて計算する方法を宣言的に記述していると考えてください。その唯一の責任は、値を計算して返すことでなければなりません。このガイドの後半では、 ウォッチャー を使って、状態の変化に反応して副作用を実行する方法について説明します。

算出した値の変更を避ける

算出プロパティから返る値は、派生され状態です。一時的なスナップショットとして考えてください。ソースの状態が変わるたびに、新しいスナップショットが作成されます。スナップショットの値を変更することは意味がないため、計算された結果は読み取り専用として扱い、変更しないようにします。その代わり、新しい計算結果が必要な場合は、依存するソースの状態を更新します。

Vue Options APIとCompostion APIの違い

これからVueJSを学ぶ方へ。

VueはReactやAngularと同じようにSPA(Single Page Application)を作るためのJavaScriptフレームワークです。

Reactが統計上では一番人気で求人も多いという事ですが、Vueは初心者にもわかりやすく、数年でReact以上の人気が出ると思います。

現段階ではVueはバージョン3まで出ており、今から始めるなら最新のVue3から学び始めて問題ありません。

Options APIとCompostion API

Options API

Vueのコードを書くにあたり2つ(正確には3つ)方法があります。

一つ目が初心者向けのOptions APIです。

まずはOptionsAPIの例を見てみましょう。

<script>
export default {
  // Properties returned from data() becomes reactive state
  // and will be exposed on `this`.
  data() {
    return {
      count: 0
    }
  },

  // Methods are functions that mutate state and trigger updates.
  // They can be bound as event listeners in templates.
  methods: {
    increment() {
      this.count++
    }
  },

  // Lifecycle hooks are called at different stages
  // of a component's lifecycle.
  // This function will be called when the component is mounted.
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Options APIの特徴はdata()でデータをまとめmethod()で使用するファンクションをまとめ、このようにブロックごとでコードを分けていくことです。

短所として、thisのキーワードを使わないといけないこと、ブロックごとでコードをまとめるのでコードの文にまとまりがなくなることです。

一応、Options APIという存在があるという事だけ覚えておいて、まったく学ぶ必要はありません。

Composition API

Vueの強みといえるCompostion APIと<script setup>を紹介します。

下記がcompostion APIの例になります。

特徴としては、

  • <script setup>が導入され少ないコードでかけるようになった。
  • setupのキーワードをscriptタグに入れた場合はvueからメソッドをインポートする必要があります。
  • 機能ごとにコードをまとめるので後から読みやすい。
  • インポートしたコンポーネントや変数をそのままHTMLのテンプレートで使用できる。
<script setup>
import { ref, onMounted } from 'vue'

// reactive state
const count = ref(0)

// functions that mutate state and trigger updates
function increment() {
  count.value++
}

// lifecycle hooks
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

ではrefやonMountのLifecycle Hook(ライフサイクルフック)は今後、基本のコンポーネントやプロップの使い方を紹介した後に詳しく説明していきたいと思います。

今日、覚えてほしいことです。

  • VueはCompostion APIの書き方で書くようにする。
  • VueはJavaScriptとhtml、cssが一つのファイルにまとまった.vueファイルで構成される。

Viteを使ってVueを始めよう

Viteとは

Vite(発音:ヴィート)はVue Cliの次世代のビルドツールで軽量でデベロップメントのスピードを速くするために作られました。

今まではnpmやvue cliでVueアプリを作ってきましたが、Viteを使ってVueだけでなくReactやSvelteも構築できてしまいます。


Viteをつかってみよう

現段階ではNode.jsの12.2.0以上のバージョンが必要とされています。ではnpm、もしくはyarnが入っていることを確認してViteを使ってみましょう。

npmの場合

npm create vite@latest

yarnの場合

yarn create vite

インストールが終わったら下のコマンドでパッケージをインストールしてデベロップメント用のウェブサーバーを起動しましょう。

  cd vite-project
  npm install
  npm run dev

index.html と プロジェクトルート

通常、Vue Cliやnpmで作成したVueアプリケーションのindex.htmlはpublicフォルダに格納されますが、Viteで作った場合はindex.htmlがプロジェクトの直下に作成されます。このファイルがアプリケーションのエントリーポイントとなっているからです。

アプリケーションのビルド

Viteでアプリケーションが完成したら下記のコマンドでコードをコンパイルしましょう。

npm run build

 

[Vue入門] テンプレートシンタックス

Vue では、HTML ベースのテンプレート構文を使用します。テンプレート構文では、基盤とするコンポーネントのインスタンスのデータと、レンダリングされる DOM を宣言的にバインドすることが可能です。すべての Vue テンプレートは、仕様に準拠しているブラウザーや HTML パーサーでパースできる、構文的に正規の HTML です。

内部では、Vue はテンプレートをコンパイルし、高度に最適化された JavaScript のコードにします。リアクティビティ機構と組み合わせ、Vue はアプリの状態が変化したとき、再レンダリングを必要とする最小限のコンポーネントをインテリジェントに見つけ出すことができます。そして、最小限の DOM 操作を適用します。

仮想 DOM の各種概念をよく知っていて、生の JavaScript が持つパワーを活かしたいという場合には、テンプレートの代わりに render 関数を直接記述することもできます。さらに、オプションで JSX もサポートされています。ただし、これらの書き方をする場合には、コンパイル時の最適化がテンプレートと同等のレベルでは利用できないことに注意してください。

テキスト展開

データバインディングで最も基本の形式は、「マスタッシュ構文」(二重中括弧) によるテキスト展開です:

<span>Message: {{ msg }}</span>

マスタッシュのタグの中身は、対応するコンポーネントのインスタンスが持つ msg というプロパティの値に置き換えられます。msg プロパティが変更されるたびに、マスタッシュの中身も更新されます。

HTMLタグを含めたデータ

マスタッシュの中では、データが HTML ではなくプレーンテキストとして解釈されます。本来の HTML を出力したい場合は、次のように v-html ディレクティブを用いる必要があります:

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Using text interpolation: <span style=”color: red”>This should be red.</span>

Using v-html directive: This should be red.

ここで、新たな要素が登場しました。この例にある v-html という属性は、「ディレクティブ」と呼ばれるものです。ディレクティブは v- という接頭辞を持ち、Vue によって提供される特別な属性であることを示します。そしてご想像の通り、ディレクティブはレンダリングされる DOM に、特別なリアクティブな振る舞いを割り当てます。この例では、簡単に言うと、「現在アクティブなインスタンスが持つ rawHtml というプロパティをこの要素の inner HTML に適用して最新に保つ」ということが書かれています。

span の中身は rawHtml プロパティが持つ値に置き換えられ、プレーンな HTML として解釈されます。データバインディングは無視されます。v-html は、テンプレートの断片を組み立てるのには利用できないことに注意してください。これは、Vue が文字列ベースのテンプレートエンジンではないためです。それに代わり、UI の再利用や組み立ての基本単位として推奨されているのが「コンポーネント」です。

セキュリティーに関する警告

ウェブサイト上で任意の HTML を動的にレンダリングすることは、クロスサイトスクリプティング (XSS) 脆弱性を招きやすく、非常に危険です。v-html は信頼できるコンテンツにのみ使用し、ユーザーから渡されるコンテンツには決して使用しないでください。

属性バインディング

HTML 属性の中ではマスタッシュ構文が使えません。代わりに、以下の v-bind ディレクティブを使用します:

<div v-bind:id="dynamicId"></div>

この v-bind ディレクティブは、要素の id という属性を、コンポーネントが持つ dynamicId というプロパティと同期させるよう Vue に指示しています。バインドされた値が null または undefined の場合、その属性はレンダリングされる要素から除外されます。

省略記法

v-bind は使用頻度が非常に高いため、以下の専用の省略記法があります:

<div :id="dynamicId"></div>

: で始まる属性は、普通の HTML の記法とは少し異なるように見えますが、実際には属性名として有効な文字です。Vue をサポートするすべてのブラウザーは、これを正しく解析することができます。なお、これは最終的にレンダリングされるマークアップには現れません。この省略記法を使うかどうかは任意ですが、その使い方を後ほど詳しく知れば、良さがわかるはずです。

このガイドの残りの部分では、Vue を用いる開発者にとって最も一般的な書き方である省略記法をコード例のなかで使用します。

ブーリアン属性

ブーリアン属性は、要素に含まれるかどうかによって「真」または「偽」の値を表す属性です。例えば、disabled は最も一般的に用いられるブーリアン属性の 1 つです。

以下のケースでは、v-bind は少し特別な動作をします:

<button :disabled="isButtonDisabled">Button</button>

この disabled という属性は、isButtonDisabled が 真値 (truthy value) である場合に要素に含まれます。また、<button disabled=""> との一貫性を保つため、値が空の文字列である場合にも含まれます。それ以外の偽値 (falsy values) の場合には、属性が要素から取り除かれます。

複数の属性を動的にバインドさせる

次のような複数の属性を持つ JavaScript オブジェクトがあるとします:

data() {
  return {
    objectOfAttrs: {
      id: 'container',
      class: 'wrapper'
    }
  }
}

以下のように v-bind を引数なしで用いると、これらの複数の属性を 1 つの要素にバインドすることができます:

<div v-bind="objectOfAttrs"></div>

JavaScript の式を用いる

ここまで、テンプレート内のプロパティのキーに単純なバインドを行う方法だけを見てきました。しかし、実は Vue ではあらゆるデータバインディングにおいて、以下のように JavaScript 式のフルパワーを活用することができます:

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

これらの式は、現在のコンポーネントインスタンスのデータスコープ内で、JavaScript の式として評価されます。

Vue のテンプレートでは、以下の場所で JavaScript の式を使用することができます:

  • テキスト展開の内部 (マスタッシュ構文内)
  • 任意の Vue ディレクティブ (v- で始まる特殊な属性) の属性値の中身

式に限られる

それぞれのバインディングには、単一の式しか含めることができません。そのため、以下はうまく動作しません:

<!-- これは文であり、式ではありません: -->
{{ var a = 1 }}

<!-- フロー制御も動作しません。代わりに三項演算子を使用してください。 -->
{{ if (ok) { return message } }}

関数の呼び出し

コンポーネントから公開されているメソッドであれば、以下のようにバインディングの式の内部で呼び出すことができます:

<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

TIP

バインディングの式の内部で呼び出される関数は、コンポーネントが更新されるたびに呼び出されます。そのため、データの変更や非同期処理をトリガーするような副作用を持たせてはいけません

グローバルへのアクセスの制限

テンプレートで用いる式はサンドボックス内で実行され、限定的なグローバルのリストにのみアクセスできます。このリストには、Math や Date などのよく使われる組み込みグローバルが含まれています。

ユーザーが window に付与したプロパティなど、このリストに明示的に含まれていないグローバルには、テンプレート内の式からアクセスすることができません。ただし、app.config.globalProperties に追加することにより、Vue のあらゆる式で利用できるグローバルを明示的に定義することができます。

ディレクティブ

ディレクティブは、v- という接頭辞を持つ特別な属性です。Vue では、上で紹介した v-html や v-bind をはじめ、数々の組み込みディレクティブが用意されています。

ディレクティブの属性値は、JavaScript の単一の式であることが期待されます (ただし v-forv-onv-slot は例外であり、後ほどそれぞれのセクションで説明します)。ディレクティブの役割は、式が示す値が変化したとき、リアクティブに更新を DOM に適用することです。例えば、v-if を取り上げてみます:

<p v-if="seen">Now you see me</p>

この例では、v-if というディレクティブを用いて、式 seen が示す値の真偽に基づいて要素 <p> を削除したり挿入したりします。

引数

一部のディレクティブは引数を取ることができます。引数は、ディレクティブ名の後にコロンで示します。以下は、v-bind ディレクティブを使って HTML 属性の 1 つをリアクティブに更新する例です:

<a v-bind:href="url"> ... </a>

<!-- 省略記法 -->
<a :href="url"> ... </a>

この例では href が引数です。これにより、要素の href という属性を url という式の値にバインドするという指示が v-bind ディレクティブに伝えられます。省略記法では、引数の前に置かれる v-bind: の部分がすべて : という 1 文字に凝縮されます。

別の例として、DOM イベントをリッスンする v-on ディレクティブを紹介します:

<a v-on:click="doSomething"> ... </a>

<!-- 省略記法 -->
<a @click="doSomething"> ... </a>

この例では、リッスンするイベント名の click が引数です。ごく一部のディレクティブには省略記法の記号として @ を持つものがあり、v-on もその 1 つです。イベントのハンドリングについても、後ほど詳しく説明します。

動的引数

ディレクティブの引数を指す部分では、以下のように角括弧で囲んだ JavaScript の式を用いることもできます:

<!--
引数で使用できる式には、いくつか制約があります。詳細は以下の
「動的引数の値に関する制約」および「動的引数の構文上の制約」セクションで説明します。
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 省略記法 -->
<a :[attributeName]="url"> ... </a>

この例では、attributeName が JavaScript の式として動的に評価され、そこで評価された値が最終的な引数を指す値として使用されます。例えば、コンポーネントのインスタンスが attributeName というデータプロパティを持ち、その値が "href" のとき、このバインディングは v-bind:href と同等になります。

同じように、動的引数を用いてハンドラーを動的なイベント名にバインドすることもできます:

<a v-on:[eventName]="doSomething"> ... </a>

<!-- 省略記法 -->
<a @[eventName]="doSomething">

この例では、eventName の値が "focus" のとき、v-on:[eventName] が v-on:focus と同等になります。

動的引数の値に関する制約

動的引数は、評価結果が null または文字列のいずれかになることが期待されます。null は特別な値で、バインディングを削除することを明示的に表します。それ以外の非文字列の値を指定すると、警告が発生します。

動的引数の構文上の制約

動的引数の式には、構文上の制約がいくつかあります。これは、スペースや引用符など特定の文字が HTML の属性名の中では無効となるためです。例えば、次のようなものは無効となります:

<!-- この場合、コンパイラーで警告が発生します。 -->
<a :['foo' + bar]="value"> ... </a>

複雑な動的引数を渡す必要がある場合は、後ほど取り上げる算出プロパティを使用するとよいでしょう。

また、HTML ファイルに直接記述する DOM 内テンプレートを使用する場合、ブラウザーでは属性名が小文字であることが求められるため、以下のように大文字のキー名を使用することは避ける必要があります:

<a :[someAttr]="value"> ... </a>

上のコードは、DOM 内テンプレートでは :[someattr] に変換されます。もしコンポーネントに someattr ではなく someAttr というプロパティしかなければ、このコードは動作しません。

修飾子

修飾子は、ドット (.) で示される特別な接頭辞で、ディレクティブを何らかの特別な方法でバインドすることを表します。例えば、以下に示す .prevent という修飾子は、イベントがトリガーされたときに event.preventDefault() を呼び出すことを v-on ディレクティブに伝えます:

<form @submit.prevent="onSubmit">...</form>

この後、v-on 向けや v-model 向けの修飾子の例を、その機能のページで見ることになるでしょう。

最後に、ディレクティブの構文の全容をこちらの図にまとめました:

VueアプリをEXEする方法

準備するもの

・Vueアプリ

・npm

イントロ

今回はVueアプリをデスクトップアプリとして使えるようにするためにElectronJSを使います。

以前もElectronJSを紹介したことがありますが、何かしらの理由でVueのクライアントアプリをネットに公開したくない場合や、ローカルのPCからAPIにアクセスできるアプリを作成したい時に最適です。

準備

では、Vueのアプリを準備しましょう。まっさらな状態から作成する場合はVue cliやViteを使ってVueアプリを作成してもらって構いません。

もし、実際のアプリケーションで参考にしてみたい場合はこちらを使ってください。

https://github.com/TraitOtaku/VueApp/tree/4_AuthReady
git clone <URL>

cd /path/to/repo/

git branch -a

git checkout 4_AuthReady
npm i

npm run serve

とりあえずアプリが起動できるところまで確認してください。

バックエンドのDjangoのAPIもこちらからどうぞ。

https://github.com/TraitOtaku/djangoAPI/tree/8_AuthReady

ElectronJSのインストール

今回はVue-Electron Builderを使ってVue CLIのコマンドでElectronをインストールしていきます。

公式サイトをチェックしましょう。

https://nklayman.github.io/vue-cli-plugin-electron-builder/

Vue CLIがない人はこのコマンドでVueCLIをインストールしましょう。

npm install -g @vue/cli

ではVueのアプリにElectronを追加します。

vue add electron-builder

デベロプメントサーバーの起動の仕方

Yarnの場合

yarn electron:serve

NPMの場合

npm run electron:serve

アプリをExe化する

Yarnの場合

yarn electron:build

NPMの場合

npm run electron:build

Vue Routerを使っている場合

Vue Routerを使っている場合は下記のような警告が出ます。

 It is detected that you are using Vue Router. It must function in hash mode to work in Electron. Learn more at https://goo.gl/GM1xZG

この場合は、router内のindex.jsのCreateWebHistoryをcreateWebHashHistoryに変えてあげましょう。

const router = createRouter({

  history: createWebHashHistory(process.env.BASE_URL),

  routes

})

もしくはこのようなやり方でもできます。

const router = createRouter({

  history: process.env.IS_ELECTRON

    ? createWebHashHistory()

    : createWebHistory(),

  routes,

});

そうするとURLがこんな感じになります。

http://localhost:8080/#/

これはSEO対策としては悪いのでEXE化するときだけに変更するように覚えておいてください。

これをしないとElectronアプリが白い画面しか表示されないエラーが発生します。

APIフェッチがビルドの時だけエラーになる

ではElectron:Buildのコマンドでログイン画面に入ろうとしてもAxiosがNetworkエラーでログインできないことがあります。

さらに、Djangoのアプリ側では200で返ってくるので謎の現象のように見えます。

これはElectronアプリのセキュリティをCORSのヘッダーのエラーにより発生しています。

まずは、backend.jsのwebSecurityをfalseにします。

※注意!

これはあくまでも検証用として記載しております。

実際にプロダクションにする場合はより安全なやり方が下記のブログで紹介されているので参考にどうぞ。

https://pratikpc.medium.com/bypassing-cors-with-electron-ab7eaf331605
const win = new BrowserWindow({

    webPreferences: {

        webSecurity: false

    }

});

これで問題なくElectronからもAxiosが使えるようになりました。

お疲れ様です。

VueRouterでログインしていないユーザーをログイン画面に誘導する方法

準備するもの

・Vue3

・Vue-Router

イントロ

今日はVue3でログインしていないユーザーをログイン画面に飛ばす方法を紹介します。

この方法以外にもこの目的を達成できる方法が色々ありますがVue-Routerを使ったやり肩が簡単だと思ったので参考にしてみてください。

ユーザーを判断するシステムを作る

Vueアプリにアクセスしたユーザーを判断するシステムがないことにはユーザを振り分けることができませんね。

今回は、ログインしていないユーザーをどうやって判断するかを紹介します。

前回紹介した記事でトークン認証を使ったログインの仕方を紹介しました。

では、これを使って、トークンのないユーザーをはじき出しましょう。

localStrageについて

今回は単純にブラウザのローカルストレージにデータを保管していますが、もし複雑なデータをあるかうようでしたらVuexというState Managementシステムでも同じことができます。

Vue Routerについて

Vue-RouterはVueのライブラリでURLのルーティング、もしくはページのコンポーネントを担当するメカニズムの事です。

Metaについて

このコードを見て分かるようにそれぞれのルートにmetaを設定することができます。

今回は一番ふさわしいrequireAutrhというキーで名前を付けましたが、adminOnlyでも、firstTimeでも何でも良いです。

で、このキーに対してtrueの値を入れてあげます。

後からこのrequireAuthがtureのルーティングに対して更に条件を足していきます。

const routes = [

  {

    path: "/",

    name: "home",

    component: HomeView,

    meta: { requiresAuth: true },

  },

  {

    path: "/login",

    name: "login",

    component: LoginView,

  },

];

beforeEachでルーティングに制限をかける

VueRouterの公式のドキュメンテーションにもありますが、このmetaに対して条件を足す場合には下記のようにコードを書いていくことになります。

パラメータではto とfromが使えますが今回はtoだけを使います。つまりは行き先だけを指定して、どこのページから来ているかのfromは使わないという事ですね。

to.meta.requiresAuthでさっきのrequireAuthがtrueであることと、 localStorage.getItemのtokenがnull、つまり、NoValueで値がない場合にどうするか指示します。

今回はバックエンドのアプリからログインのトークンをもらっていない場合はloginのページに誘導することにしています。

router.beforeEach((to) => {

  if (to.meta.requiresAuth && localStorage.getItem("token") == null) {

    return { name: "login" };

  }

});

では、今日はこれくらいで。

Djangoのユーザーモデルとアドミンパネルをカスタムしよう

準備するもの

・Python3

・Djangoの基礎知識

イントロ

DjangoでデフォルトでついてくるユーザーモデルはEmailと名前くらいのシンプルなものですね。

今日は、Djangoについてくるユーザーモデルにこれから使う、従業員管理に合わせてモデルを追加していきます。

今日紹介する内容はこのリンクからも公式ドキュメントで確認できます。

https://docs.djangoproject.com/en/4.0/topics/auth/customizing/

解決できる課題

Djangoのユーザーモデルに従業員の電話番号などの情報を追加できるようになる。

Djangoのアドミンパネルで検索機能を付ける。

Djangoのアドミンパネルに表示するデータを増やす。

下準備

今回は、従業員のチケット管理アプリのバックエンドを想定したサンプルコードを紹介します。

下から完成したサンプルコードをダウンロードしてください。

https://github.com/TraitOtaku/djangoAPI/tree/9_CustomUserModel

ではいつものようにgitのコマンドを使ってリポジトリをクローンしましょう。

Gitの使い方はこちらの動画で説明しています。

git clone https://github.com/TraitOtaku/djangoAPI.git

cd DjangoAPI

git branch -a

git checkout 9_CustomuserModel

バーチャル環境を設定しましょう。

pip install virtualenv

virtualenv env

env\Scripts\activate

pip install -r requirements.txt

Djangoを設定していきます。

python manage.py makemigrations

python manage.py migrate

python manage.py createsuperuser

python manage.py runserver

カスタムUserモデルを作ろう

まずは、デフォルトのDjangoのユーザーモデルをインポートして上書きしていく作業になります。

下記のモジュールをインポートしています。

from django.utils.translation import gettext_lazy as _

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager

ユーザーモデル

では、下記のモデルを見てみましょう。

AbstractBseUserからDjangoのUserモデルを引っ張ってきています。

class Member(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(_('email address'), unique=True)

    user_name = models.CharField(max_length=150, unique=True)

    first_name = models.CharField(max_length=150, blank=True)

    last_name = models.CharField(max_length=150, blank=True)

    phone = models.CharField(max_length=150, blank=True)

    about = models.TextField(_(

        'about'), max_length=500, blank=True)

    is_staff = models.BooleanField(default=False)

    is_active = models.BooleanField(default=False)



    objects = CustomAccountManager()



    USERNAME_FIELD = 'email'

    REQUIRED_FIELDS = ['user_name', 'first_name']



    def __str__(self):

        return self.user_name

USERNAME_FIELD = ’email’ でログインする際のユーザーネームの代わりにEmailとパスワードでログインできるようになります。

REQUIRED_FIELDSはユーザーを作成する際に必須な項目の事です。

objects = CustomAccountManager()でアドミンの権限のあるユーザーの設定を上書きします。

BaseUserManager

今度は、アドミン権限のあるユーザーの設定を上書きしていきます。

class CustomAccountManager(BaseUserManager):



    def create_superuser(self, email, user_name, first_name, password, **other_fields):



        other_fields.setdefault('is_staff', True)

        other_fields.setdefault('is_superuser', True)

        other_fields.setdefault('is_active', True)



        if other_fields.get('is_staff') is not True:

            raise ValueError(

                'Superuser must be assigned to is_staff=True.')

        if other_fields.get('is_superuser') is not True:

            raise ValueError(

                'Superuser must be assigned to is_superuser=True.')



        return self.create_user(email, user_name, first_name, password, **other_fields)



    def create_user(self, email, user_name, first_name, password, **other_fields):



        if not email:

            raise ValueError(_('You must provide an email address'))



        email = self.normalize_email(email)

        user = self.model(email=email, user_name=user_name,

                          first_name=first_name, **other_fields)

        user.set_password(password)

        user.save()

        return user

Settings.pyの設定

このファイルのどこかに下記のラインを追加しましょう。

これでDjangoがMemberモデルを使ってユーザー認証をするように指示できます。

AUTH_USER_MODEL = 'members.Member'

Admin.pyの設定

ではアドミンパネルの表示をカスタマイズしていきます。

from django.contrib import admin

from .models import Member, Office

from django.contrib.auth.admin import UserAdmin



class UserAdminConfig(UserAdmin):

    model = Member

    search_fields = ('email', 'user_name', 'first_name',)

    list_filter = ('email', 'user_name', 'first_name', 'is_active', 'is_staff')

    ordering = ('email',)

    list_display = ('email', 'user_name', 'first_name',

                    'is_active', 'is_staff')

    fieldsets = (

        (None, {'fields': ('email', 'user_name',

         'first_name', 'last_name', 'phone', 'password')}),

        ('Permissions', {'fields': ('is_staff', 'is_active')}),

    )

    # formfield_overrides = {

    #     NewUser.about: {'widget': Textarea(attrs={'rows': 10, 'cols': 40})},

    # }

    add_fieldsets = (

        (None, {

            'classes': ('wide',),

            'fields': ('email', 'user_name', 'first_name', 'phone', 'password1', 'password2', 'is_active', 'is_staff')}

         ),

    )



admin.site.register(Office)

admin.site.register(Member, UserAdminConfig)

見たままの感じですがこれをもとにするとアドミンパネルももっと使いやすくなりますね。

ではおつかれさまです。