Wowgineer Notes

WowTechエンジニアの徒然開発日記

Wowgineer Notes

〜新たな"Wow"を生み出す〜

Redux Toolkit を導入してみたら、とても良かった!!!

Web開発チームの山本です。

Reactで作成した既存のアプリケーションにRedux Toolkitを導入してみたのですが、想像以上によかったので、今回はRedux Toolkitについて紹介します。

 

Redux Toolkit とは

Reduxを効率的に開発するために作られた公式のツールセットです。 Reduxの公式ドキュメントにもRedux Toolkitについて書かれています。Reduxの公式ドキュメントでも、Redux Toolkitを使うことを推奨しており、Reduxをこれから始める人やシンプルにRedux書きたい人にも良さそうです。Redux Toolkitには、Immer、Reselect、Redux-Thunkも含まれているので、自分でインストールする必要がないので、助かります。

 

インストール

既存のアプリケーションにReduxを導入するときは、Redux ToolkitとReact Reduxをインストールしましょう。React Reduxは、Redux用の公式React UIバインディングレイヤーです。Redux自体はどんなフレームワークとも一緒に使用することができるので、ReduxとReactを一緒に使う場合は、React Reduxも使ってこの2つのライブラリを結びつける必要があります。詳しくはこちら

npm install @reduxjs/toolkit react-redux

 

Redux Toolkitを使う前は、Middlewareとしてredux-loggerを入れてStoreの更新のログをコンソールに表示していたのですが、ChormeのデベロッパーツールにReduxの拡張機能が使えるようになったので、redux-loggerは入れなくてもいいと思います。Reduxの拡張機能でStoreのデータを確認できたり、アクションの呼び出しも追跡できます。

 

ReducerとActionを一緒に書ける

下記はRedux Toolkitで使用されているAPIです。これまでは、ActionとReducerを別のファイルで書いたのが、Redux Toolkitではシンプルにかけるようになったので、同じファイル内で管理できるようになりました。

 

これまでReduxでアクションを定義する時は、アクションタイプの定数と、そのタイプのアクションを構築するためのアクションクリエーター関数を別々に宣言していました。Reduxのreducerは、switch文を使って実装されることが多く、扱われるアクションタイプごとに1つのケースが必要です。

Action


const RECEIVE_All_TEMPLATE_DATA = 'RECEIVE_All_TEMPLATE_DATA' function set_all_template_data(data) { return { type: RECEIVE_All_TEMPLATE_DATA, payload:data, } }

Reducer

const initialState = {templates: [] }

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'RECEIVE_All_TEMPLATE_DATA':
      return { ...state, templates: [ ...action.data]}
    default:
      return state
  }
}

Redux ToolkitのcreateActionとcreateReducerを使うと、上記のコードをシンプルに書くことができます。

import {  createAction, createReducer } from '@reduxjs/toolkit';


const setAllTemplates = createAction("RECEIVE_All_TEMPLATE_DATA"); const initialState = { templateArray:[] } const templateReducer = createReducer(initialState, (builder) => { builder .addCase(setAllTemplates, (state, action) => { state.templateArray = action.payload }) });

 

またcreateSliceを使うと、Actionと Reducerも一緒に書くことができます。アクションタイプの定数やswitch文がなくなり、かなりコード自体も見やすくなり、コード量も少なくなったので、嬉しいです。actionのpayloadの値をカスタマイズしたい時は、createSliceではprepareを使えばできるので、私は基本的にcreateSliceを使っています。

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
templateArray:[]
}

const templateSlice = createSlice({ name: 'templates', initialState: initialState, reducers: { setAllTemplates: (state, action) => { state.templateArray = action.payload } } })

 

 TypeScriptに対応している

Redux Toolkitのドキュメントに、TypeScriptでの使い方も書かれているので、TSを使用している既存サービスにRedux Toolkitを導入してもサンプルコードも豊富なため、特に大きなハードルはありませんでした。

 

Storeの設定やHooksの設定もサクッとできました。

app/store.ts

import { configureStore } from '@reduxjs/toolkit'
import reducer from '../reducers'

export const store = configureStore({
  reducer
})

export type RootState = ReturnType<typeof store.getState>

export type AppDispatch = typeof store.dispatch

app/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

// 元からあるuseDispatchやuseSelectorの代わりに、アプリ全体で使用します
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

 

ただ、createAsyncThunkで結構ハマってしまいました。3つのパラメーターの型定義が、複雑で最初は混乱しました。この方のブログ記事に詳しく書かれており、とても助かりました。ありがとうございます。createAsyncThunkは、非同期リクエストにおける各ライフサイクル毎(非同期処理中、非同期処理の成功時、非同期処理の失敗時)に処理をフックすることができるので、便利です。

 

まとめ

以前は、複数のファイルをまたがってReduxの設定をしていたのですが、Redux Toolkitによって、かなりシンプルに書けるようになったので嬉しいです。拡張機能もまだ触りきれていない部分もあるので、引き続き色々と試していきたいです。

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

入社後半年経過して社内ツールを開発した話

SREチームの三浦です。

入社して半年が経過しました。SREの取組みとして開発チームであった課題を解決するために、社内ツールを作成しましたので紹介していきます。

課題

当社のサービスWowTalkのサーバーはいくつか環境があり、バージョン管理の手法が担当者に委ねられていた問題がありました。

Ex) A環境 API: v2.5.0  Web: v1.4.0 / B環境 API: v2.6.0  Web: v1.4.0 

 → APIのバージョンの違いを確認することをわかりやすくしたい

なので、今回はバージョン管理を一元化し、チーム全員が一覧で確認できるツールを開発しました。

追加で、GitLabのリリースノートにも反映してほしいと要望がありましたので、連携できる機能を開発しました。

 

採用技術

<バックエンド>

・Express

別の社内ツール用で構築されていたので、併用して使う事にしました。

主にAPIとして使用します。

expressjs.com

・Firestore Database 

FirebaseのNoSQLデータベースサービスです。当社のサービスで一部使用しているので、採用しました。

 

firebase.google.com

 

<フロントエンド>

・Next.js

Reactのフレームワークです。コンポーネントが充実していたり、SSRやSSGにも対応しています。2020年のバージョンアップでかなり機能が充実し、勢いがあるフレームワークです。

下記の記事よりも、Next.jsの人気が高くなっていることがわかります。オススメのフレームワークです。

www.publickey1.jp

nextjs-ja-translation-docs.vercel.app

 ・Ant Design

ReactのUIフレームワークです。こちらも当社サービスで一部採用していたので、使用しました。標準で便利なコンポーネントが準備されているので、自作のコンポーネントは作成せずにAnt Designのコンポーネントを利用しました。

ant.design

 

システム構成図

f:id:miura1213:20210402155321p:plain

①社内環境の都合上、Next.jsで静的ファイルを作成してサーバーに置いています。

②Next.jsで作られた静的ファイル上で、Expressで作成したAPIをfetchして、RESTAPIを実現してます。

③GitLab APIを利用して、タグに紐づくリリースノートを更新するAPIもExpress
で開発をしています。

 

<UI>

f:id:miura1213:20210402164013p:plain

・全体をAntDesign感で統一しています。

・上記ツールに登録されたバージョン情報はGitLabにも反映されます。

 

開発した感じたこと

<よかったこと>

・普段は当社サービスの機能追加や改修を行うことが多いが、ゼロから設計してツールを作成したことは良い経験になった。

(通常はフロントメインの業務も多いが、今回はREST APIを学ぶ機会があってよかった。)

・自分の興味のある技術を採用して、楽しく開発できたこと(一番重要)。

 

<反省点>

・学習時間が長くて、期間ギリギリで終わったこと。

・運用面を考慮できずに、機能の修正を何回もしたこと。

・ファイル名やDBの構成を何度も変更して時間がなくなったこと。

 

これから運用していきますが、課題→改善の繰り返しが重要なので日々改善していこうと思います。

当社ではこの様に様々なことにチャレンジできる環境があります

 

当社ではこの様に挑戦できる環境があります。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

 

技術開発部のテレワーク事情が気になるから調査してみた

Web開発チームの山本です。

ワウテックではコロナ以前からテレワークの制度は導入されていたのですが、2020年の緊急事態宣言からテレワークに切り替わりました。現在も会社からはテレワークを推奨されているため、基本テレワークで業務を行なっています。そんなテレワーク中心になってから1年近く経ち部内のみんなが、どのようにテレワークをしているか気になるので、調査してみました。

 

1. シゴト環境

モニターや椅子などを揃えて、工夫してる人が多い

モニターや椅子など、皆さんテレワークでも仕事できる環境を整えている印象でした。やはり長時間の座り仕事なので、アーロンチェアを買っている人もいました(すごく羨ましい!)。お値段張りますが、雲の上にも座っているみたいと例えられるのが納得できるほどの価値はあるようです(本人談)。

 

モニターを使っている人も多く、 Philipsの4Kモニターを2台同時利用するのをオススメしている人もいました。PCだけでなく、モニターがあると開発が捗りそうです。 

 

仕事のお供にコーヒー必須

テレワーク中の必須アイテムとして、コーヒーと答える人が多かったです。コーヒーメーカーで決まった回数飲む人もいれば、冷めないサーモスのステンレスマグカップがいいなど、各々自分に合うコーヒーのスタイルがありそうです。コーヒー党の一方で、はちみつ紅茶を飲んでいる人もいるので、技術部は全員がコーヒーを飲むわけではないですよ(念のため)。

 

コーヒーを飲む人の中には、デロンギでコーヒーライフを充実されている人もいました。全自動なのが、いいですね。お手頃なコーヒーメーカーだとシロカも使っている方がいるようです。

barista.delonghi.co.jp

 

 

また行き詰まった時のお散歩や音楽聴いたりと、家にいる時間が長いので、それぞれちょっとした息抜きもしているようです。仕事柄座っている時間が多いので、テレワークになってから運動不足や肩が以前よりもコルというのを聞くようになりました。そんな時に、ストレッチで肩こり解消する本もあるようなので、これで体調も整えたいです。

 

開発ツールではGitLab便利

開発ツールとしてGitLabを使ってプロジェクト管理などをしているので、テレワークでも問題なく仕事を進めることができています。スケジュール管理などもGitLabで行おうとする動きも最近あるので、それに関しては、主軸となっているメンバーが別のブログで書いてくれたらいいなと思っています(お願いします!!!)。

about.gitlab.com

 

2. 働き方

テレワークの課題

基本的に開発部はテレワークで作業しているので、テレワークになってコミュニケーションが取りづらくなったと感じているか?とメンバーに聞いてみました。そしたら、ほとんどのメンバーが、何かしらのコミュニケーション面での課題を感じたことがあるという結果になりました。

f:id:yamamoto5555:20210226113017p:plain

テレワークになってコミュニケーションが取りづらくなったと感じているか?

 

具体的には、小さな疑問なども気軽に聞くことが難しくなったという声が多かったです。今までは、確認したいことも隣にいたので、簡単に聞くことができていたのが、テレワークによって質問するハードルが少し上がってしまったということでした。

 

一方で、進歩確認などは課題は少ないという声もありました。チームによっては毎日1on1をすることで、業務に対する疑問を解消するようにしているところもあります。

 

チャットだけでなくテレビ電話も一緒に使う

テレワーク時にコミュニケーションで気をつけていることや工夫していることがあるのかを聞いてみると、文章だけでは伝わりにくいことは、通話やテレビ電話などでサクッと聞くという声がありました。文章だと複雑になってしまうこともあるのですが、意外と電話すると直ぐに解決できることも実体験としてはあります。

 

また、文章にする際は主語や述語などの表現に気をつけながら、誤解が起きにくいように気をつけていたり、文章だけでは淡白になってしまいがちなので、スタンプを使うようにするという工夫をしているようです。近くにいないからこそ、少し丁寧なコミュニケーションを心掛ける必要があるのかもしれません。

 

3. 今後の課題

 顔を合わせることが少なくなったことで、雑談の機会は減ったように感じます。特に他の部署と話す機会は少なくなってしまいました。開発部では、ちょっとした雑談もできるチャットルームがあり 、日頃の情報共有などを行なっていますが、隣にいるような雑談とは少し違います。まだまだ課題は多いですが、テレワークで仕事する状態は続きそうなので、これからも改善できる部分には取り組んでいきたいです。

 

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

初めてVue + VuexでNewsAPIを使ったアプリ作ってみた

Web開発チームの山本です。

普段はReactで開発をしているのですが、Vueには触れたことがなかったので、簡単なアプリを作成してみました。今回は、ニュース記事を表示できるアプリについて紹介します。

 

 

アプリ概要

今回は、News APIを使ってカテゴリーごとに記事を表示させていきます。ただVueに集中したかったので、フロントのみを実装しています。

 

またVuexも使ってみたかったので、ブックマーク機能を追加しました。ブックマークした記事は、ブックマークのページでまとめて表示することもできます。

 

f:id:yamamoto5555:20210129113252p:plain

 

開発環境

私の開発環境は次のようになっています。

  • MacBookPro
  • Node.js v12.16.2
  • npm 6.14.4

準備

Vue CLIをインストール

Vue CLIは、Vue.jsを迅速に開発するためのフルシステムです。ReactでいうCreate React Appに近いかなと思います。Vue CLIは、基本的に必要なことをやってくれるので、とても助かります。ありがたや。

$npm install -g @vue/cli
$vue --version
@vue/cli 4.5.8

 

プロジェクトの作成

news-appというプロジェクトを作成します。

$vue create news-app
$cd news-app
//サーバーを起動 $npm run serve

 ブラウザでhttp://localhost:8081/ を開くと、サンプルが表示さます。サンプル表示させただけですが、表示できると嬉しいですね!

 

f:id:yamamoto5555:20210108111443p:plain 

Vue用のUIライブラリ

今回UIライブラリにVue Materialを使用します。雰囲気Google Newsぽくなるかなと思ったからです(なって欲しい)。

$npm install vue-material --save

 

公式ドキュメントで必要なコンポーネントだけをimportすることが推奨されていたので、main.jsで使いたいコンポーネントだけをimportしていきます。

import Vue from 'vue'
import App from './App.vue'
import { MdButton, MdCard, MdLayout, MdApp, MdList, MdToolbar, MdDrawer, MdIcon, MdContent } from 'vue-material/dist/components'
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'

Vue.config.productionTip = false

Vue.use(Vuex)
Vue.use(MdButton)
Vue.use(MdCard)
Vue.use(MdLayout)
Vue.use(MdApp)
Vue.use(MdList)
Vue.use(MdToolbar)
Vue.use(MdDrawer)
Vue.use(MdIcon)
Vue.use(MdContent)

News APIのアカウント登録

表示させるニュースは、NewsAPIを使っていきます。アカウントを作成すると、API keyを取得できます。NewsAPIは、カテゴリーごとや言語ごとにニュースを取得できるので便利でした。

Vueでコンポーネント作成

Vue Materialを使用して、Headerを作成しましょう。Vue MaterialのComponentsの中のAppを使いました。

src/components/App.vue

<template>
  <div id="app">
    <div class="page-container">
      <md-app md-mode="reveal">
        <md-app-toolbar class="md-primary">
          <md-button class="md-icon-button">
            <md-icon>menu</md-icon>
          </md-button>
          <span class="md-title">News App</span>
        </md-app-toolbar>
      </md-app>
    </div>
  </div>
</template>
<script> export default { name: 'App', } </script>
<style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>

上記のコードをかくと、http://localhost:8081/に下のような画像のようにHeaderが表示されます。

f:id:yamamoto5555:20210120110856p:plain

同じように下記のコンポーネントを作成していきます。

  • NewsItem.vue:ニュースを表示するカードのコンポーネント
  • Content.vue:複数のカードを表示されるコンポーネント

src/components/NewsItem.vue

<template>
    <md-card>
      <md-card-area>
          <md-card-media>
          </md-card-media>
          <md-card-header>
            <div class="md-title">Article Title</div>
          </md-card-header>

          <md-card-content>
            Content.........
          </md-card-content>
        </md-card-area>
        <md-card-actions md-alignment="space-between">
          <md-button class="md-icon-button" >
            <md-icon>bookmark</md-icon>
          </md-button>
          <md-button class="md-primary">Read More</md-button>
        </md-card-actions>
    </md-card>
</template>

 Vue MaterialにCardというコンポーネントがあるので、それを使用してカードを作成します。

 

src/components/Content.vue

<template>
  <div class="md-layout md-gutter md-alignment-center">
    <div v-for="n in 8" class="md-layout-item" :key="n">
<NewsItem :article="n"/>
</div> </div> </template> <script> import NewsItem from "./NewsItem.vue" export default { components: { NewsItem }, } </script>



 NewsItem.vueをContent.vueで呼び出し複数のカードを表示していきます。

作成した Content.vueをApp.vueで読み込んでみましょう。

src/components/App.vue

<template>
  <div id="app">
    <div class="page-container">
      <md-app md-mode="reveal">
        <md-app-toolbar class="md-primary">
          <md-button class="md-icon-button">
            <md-icon>menu</md-icon>
          </md-button>
          <span class="md-title">News App</span>
        </md-app-toolbar>
        <md-app-content>
          <Content />
        </md-app-content>
      </md-app>
    </div>
  </div>
</template>
<script>

export default {
import Content from './components/Content.vue'
  name: 'App',
components: {
    Content
  },
}
</script>

 

そうすると、カードを複数表示するコンポーネントが完成します。UIライブラリを使用すれば、そんなに時間がかからず作成できるはずです。

f:id:yamamoto5555:20210120114053p:plain



 

Vuexの作成

 次に、登録したNewsAPIを使用して、ニュースのデータを取得し表示します。さらに、次の3つのことを実現させたいです。

  • カテゴリーごとに記事を表示させる
  • 記事をブックマークできる
  • ブックマークされた記事は一覧で表示できるようにする

上記のことを実現するために考えなくてはいけないのは、記事とブックマークされた記事のデータ管理です。ただこれをVueで行うと、データの受け渡しをpropsで行うことになり、複雑化してしまいます。。。今回は、Vuexを使用してデータ一元化を行ないます。

 

Vuexとは

ReactでいうReduxのように、Vue.js アプリケーションのための状態管理のライブラリです。詳しいことは、公式ドキュメントに書かれています。

 

Vuexの導入

Vuexをインストールしましょう。

$npm install vuex --save

インストールしたら、main.jsを編集しVuexの設定をします。

import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
import store from './store'


Vue.config.productionTip = false

Vue.use(Vuex)


new Vue({
  render: h => h(App),
  store,
}).$mount('#app')

「import store from './store'」と記載していますが、storeのディレクトリは作成していなかったので、作っていきます。

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import news from './modules/news'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
  modules: {
    news,
  },
  strict: debug,
})

 src/store/modules/news.js

// initial state
const state = () => ({
})

// getters
const getters = {}

// actions
const actions = {
}

// mutations
const mutations = {
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

 

stateの作成

stateは、アプリケーション全体のデータを管理するところです。今回のアプリでは、3つのデータを保存していきます。

  • カテゴリーごとに表示するニュースのデータ
  • 選択しているカテゴリー状態
  • ブックマークしているデータ

newsDataはカテゴリーごとにデータを取り出しやすくしたかったので、keyをカテゴリー名にしています。

 src/store/modules/news.js

const state = () => ({
    newsData: {
        "technology":[],
        "entertainment":[],
        "business":[],
        "science":[],
    },
    activeCategory: "business",
    bookmarkData:[]
})

 

NewsAPIでデータ取得

保存するニュースのデータをNewsAPIから取得します。指定したカテゴリーのデータを取得したかったので、引数でカテゴリー名を渡しています。APIからデータ取得するときに、axiosを使用しました。詳しい使い方は、ドキュメントをご覧ください。

import axios from 'axios' 

const key = NewsAPIの登録後に渡されるAPI key export const changeCategory = async (category) => { const res = await axios.get('https://newsapi.org/v2/top-headlines?country=jp&category='+category+'&apiKey='+ key) const articles = res.data.articles; return articles }
actionの作成

actionでAPIの実行をしましょう。初回のみデータ取得したいので、既にカテゴリーのデータがある場合には、更新せずにreturn falseしています。また、本当はサーバー側でidの割り振ると思うのですが、今回はフロントだけで処理を行なっているため、article_id_listというのを使って、idを割り振っています。ニュースデータは固定で20件ずつ取るようにしているので、article_id_listのstart_indexを20ごとに決めています。

import { changeCategory } from '../../../api/news.js'

const article_id_list = {business: 0, entertainment: 20, technology: 40, science: 60} const actions = { async getArticles({commit, state}){ const category = state.activeCategory if(state.newsData[category].length ){ return false }else{ const new_articles = await changeCategory(category) const article_data = new_articles.map((article, index)=>{ const start_index = article_id_list[category] article.id = index + start_index article.bookmark = false return article }) commit('setArticles', {data: article_data, category: category}) return new_articles } } } 

カテゴリーの変更をするactionを追加しましょう。

import { changeCategory } from '../../../api/news.js'

const article_id_list = {business: 0, entertainment: 20, technology: 40, science: 60} const actions = { async getArticles({commit, state}){ const category = state.activeCategory if(state.newsData[category].length ){ return false }else{ const new_articles = await changeCategory(category) const article_data = new_articles.map((article, index)=>{ const start_index = article_id_list[category] article.id = index + start_index article.bookmark = false return article }) console.log("article_data", article_data) commit('setArticles', {data: article_data, category: category}) return new_articles } },
  //カテゴリーの変更をするaction追加 updateCategory({commit, state}, category){ if(state.activeCategory !== category){ commit('setActiveCategory', category) } } }

 

mutationの作成

 mutationでデータの変更をしていきます。

const mutations = {
//取得した記事のデータを保存 setArticles(state, data){ const news_data = data.data const news_category = data.category state.newsData[news_category] = news_data },
  //選択中のカテゴリーを保存 setActiveCategory(state, category){ state.activeCategory = category } }

 記事のデータは、全てを渡すのではなく、選択しているカテゴリーのデータだけを渡すようにgettersの中に書いていきます。

const getters = {
    getArticlesByCategory: (state) => {
        const category = state.activeCategory
        return state.newsData[category]
    }
}

機能の実装

ニュースの表示

App.vueで記事のデータを取得します。createdの中で、getArticlesのアクションを実行しましょう。mapGettersを使って、Component内でstoreに保存した記事のデータを取り出します。そして、Content Componentにarticlesを渡していきます。 

src/components/App.vue

<template>
  <div id="app">
    <div class="page-container">
      <md-app md-mode="reveal">
        <md-app-toolbar class="md-primary">
          <md-button class="md-icon-button">
            <md-icon>menu</md-icon>
          </md-button>
          <span class="md-title">News App</span>
        </md-app-toolbar>
        <md-app-content >
          <Content :articles="articles"/>
        </md-app-content>
      </md-app>
    </div>
  </div>
</template>

<script>
import Content from './components/Content.vue'
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'App',
  components: {
    Content
  },
    computed: {
      ...mapState({
        activeCategory: state => state.news.activeCategory,
      }),
      ...mapGetters('news', {
        articles: 'getArticlesByCategory'
      })
       
    },
    created () {
       this.$store.dispatch('news/getArticles')
  },
}
</script>

src/components/Content.vue

<template>
  <div class="md-layout md-gutter md-alignment-center">
    <div v-for="article in articles" class="md-layout-item" :key="article.title">
      <NewsItem :article="article"/>
    </div>
  </div>
</template>

<script>
import  NewsItem  from "./NewsItem.vue"

export default {
  components: {
    NewsItem
  },
  props: {
      articles: Array
  }
}

</script>

  

記事によっては、画像データがないのもあったので、画像がない場合は、no_image.jpgを表示するようにしています。

src/components/NewsItem.vue

<template>
    <md-card :id="article.id">
      <md-card-area>
          <md-card-media>
            <img :src="article.urlToImage" :alt="article.title" @error="noImage">
          </md-card-media>

          <md-card-header>
            <div class="md-title">{{article.title}}</div>
          </md-card-header>

          <md-card-content>
            {{article.description}}
          </md-card-content>
        </md-card-area>
        <md-card-actions md-alignment="space-between">
          <md-button  v-bind:href="article.url" class="md-primary">Read More</md-button>
        </md-card-actions>
    </md-card>
</template>

<script>
  import AssetsImage from "@/assets/no_image.jpg";  
  export default {
    name: 'NewsItem',
    props: {
      article: Object
    },
    data(){
      return {
        assetImage: AssetsImage
      }
    },
    methods: {
      noImage(element){
        element.target.src = this.assetImage
      }
    }
  }
</script>

 これで最初の画面に、ニュースを表示できるようになりました。

f:id:yamamoto5555:20210129113631p:plain


カテゴリー選択

ニュースデータの取得と表示はできたので、カテゴリーを選択して表示できるようにします。まずは、Drawerの左上のアイコンをクリックした時に表示/非表示できるようにします。これもVue MaterialのDrawerのコンポーネントを使っていきます。SideMenu.vueの中に、Drawer内に表示するものを書いていきます。

 

src/components/SideMenu.vue

<template>
    <div>
        <md-toolbar class="md-transparent" md-elevation="0">Category</md-toolbar>
        <md-list>
        <md-list-item class="item-active">
            <md-icon>business</md-icon>
            <span id="business" class="md-list-item-text" @click="setResource('business')">Business</span>
        </md-list-item>

        <md-list-item>
            <md-icon>music_note</md-icon>
            <span id="entertainment" class="md-list-item-text" @click="setResource('entertainment')">Entertainment</span>
        </md-list-item>

        <md-list-item>
            <md-icon>emoji_objects</md-icon>
            <span id="technology" class="md-list-item-text" @click="setResource('technology')">Technology</span>
        </md-list-item>

        <md-list-item>
            <md-icon>science</md-icon>
            <span id="science" class="md-list-item-text" @click="setResource('science')">Science</span>
        </md-list-item>
        <md-list-item>
            <md-icon>bookmarks</md-icon>
            <span id="bookmark" class="md-list-item-text" @click="setResource('bookmark')">Bookmark</span>
        </md-list-item>
        </md-list>
    </div>
</template>

<script>
    export default {
        name: 'SideMenu',
        props:{
            setResource: Function
        },

}
</script>

<style>
.md-list-item{
    cursor: pointer;
}

.md-list .item-text-active{
    color:#448aff;
}
</style>

SideMenu.vueをApp.vueから呼び出します。このDrawerの表示/表示のデータは、storeに入れずに、menuVisibleとしてlocalで管理します。そして、setResourceというメソッドで、カテゴリーを変更していきます。

 

 src/components/App.vue

<template>
  <div id="app">
    <div class="page-container">
      <md-app md-mode="reveal">
        <md-app-toolbar class="md-primary">
          <md-button class="md-icon-button" @click="menuVisible = !menuVisible">
            <md-icon>menu</md-icon>
          </md-button>
          <span class="md-title">News App</span>
        </md-app-toolbar>
        <md-app-drawer :md-active.sync="menuVisible"  >
           <SideMenu  :setResource = "setResource" />
        </md-app-drawer>
        <md-app-content>
     <Content :articles="articles"/>
    </md-app-content> </md-app> </div> </div> </template> <script> import Content from './components/Content.vue' import SideMenu from './components/SideMenu.vue' import { mapState, mapGetters } from 'vuex' export default { name: 'App', components: { SideMenu, Content }, data() { return { menuVisible: false, } }, ・




methods: {
setResource(category){
  this.$store.dispatch('news/updateCategory', category)
  this.menuVisible = false
}
}
} </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } .md-drawer { width: 230px; max-width: calc(100vw - 125px); } .md-content{ height: calc(100vh - 64px) } </style>

 

左上のアイコンをクリックすると、下のようにDrawerが表示されるようになりました。各カテゴリーをクリックしても、カテゴリー毎のニュースも表示されるようになっていると思います。

f:id:yamamoto5555:20210129195241p:plain

 

ニュースのブックマーク

actionとmutationの追加からしていきます。

  src/store/modules/news.js

const actions = {
・




  //ブックマークデータの更新のactionを追加 updateBookmark({commit}, value){ commit('updateBookmark', value) } }

  src/store/modules/news.js

const mutations = {





  //ブックマークした記事の保存または削除 updateBookmark(state, id){ let category = state.activeCategory
//既にブックマークされているか const bookmark_index = state.bookmarkData.findIndex((item) => item.id == id)
      if(category !== null && bookmark_index !== -1){ category = state.bookmarkData[bookmark_index].category }
const article_index = state.newsData[category].findIndex((item) => item.id == id) const bookmark = state.newsData[category][article_index].bookmark
if(article_index != null){
//newsDataのbookmarkをtrue・falseを変更 state.newsData[category][article_index].bookmark = !bookmark //ブックマークされていなかったら、bookmarkDataにデータを追加 if(bookmark_index === -1){ let copy_data = Object.assign(state.newsData[category][article_index]) copy_data.category = category state.bookmarkData.push(copy_data) }else{
         //既にある場合は、bookmarkDataから削除 state.bookmarkData.splice(bookmark_index, 1) } } } }

 mutationsのupdateBookmarkで、bookmarkDataとnewsDataの更新をしていきます。

 

activeCategoryがbookmarkの時は、bookmarkDataを渡すようにgettersを変更します。

  src/store/modules/news.js

const getters = {
    getArticlesByCategory: (state) => {
       const category = state.activeCategory
    let data
    if(category === "bookmark"){
     data = state.bookmarkData
    }else{
     data = state.newsData[category]
    }
     return data; } }

 

次に、クリックイベントを追加するために、コンポーネントの変更を行います。

src/components/NewsItem.vue

<template>
    <md-card :id="article.id">
      <md-card-area>
          <md-card-media>
            <img :src="article.urlToImage" :alt="article.title" @error="noImage">
          </md-card-media>

          <md-card-header>
            <div class="md-title">{{article.title}}</div>
          </md-card-header>

          <md-card-content>
            {{article.description}}
          </md-card-content>
        </md-card-area>
        <md-card-actions md-alignment="space-between">
          <md-button class="md-icon-button" @click="onChangeBookmark(article.id)">
            <md-icon v-if="article.bookmark">bookmark</md-icon>
            <md-icon v-else>bookmark_border</md-icon>
          </md-button>
          <md-button  v-bind:href="article.url" class="md-primary">Read More</md-button>
        </md-card-actions>
    </md-card>
</template>

<script>
  import AssetsImage from "@/assets/no_image.jpg";  
  export default {
    name: 'NewsItem',
    props: {
      article: Object
    },
    data(){
      return {
        assetImage: AssetsImage
      }
    },
    methods: {
      onChangeBookmark(id){
        this.$store.dispatch('news/updateBookmark', id)
      },
      noImage(element){
        element.target.src = this.assetImage
      }
    }
  }
</script>

ブックマークのアイコンをクリックすると、onChangeBookmarkの関数から、先ほど追加したupdateBookmarkのアクションが実行されます。

 

これで、ブックマークの機能が完成しました!

f:id:yamamoto5555:20210201115440g:plain

 

まとめ

初めてVueとVuexを使ってアプリを作りましたが、初心者でもReactに比べて時間をかけずに作成することができました。まだまだこのアプリの改善したいところもありますが、短時間で動くアプリを作るのを目標に作ってみました。これから、Vueを使っていく上で、様々な疑問が出てくると思うので、その時はまた別の記事でまとめたいです。

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

業務未経験で入社して3ヶ月経って感じた事

f:id:miura1213:20201216213358j:plain

こんにちは。WowDesk担当の三浦です。
今回が初投稿となります。よろしくお願いいたします。
私が当社に入社して3ヶ月が経ちましたので所感を書いていこうと思います。
ワウテックでエンジニアになりたいと思う方の参考になればと思います。

簡単な経歴

前職では、自社サービスの保守・運用・導入がメインのIT企業に約4年いました。
その後、本格的に開発に携わりたいという気持ちが強くなりエンジニアになる道を目指し、プログラミングの学習を開始しました。
そして、当社と縁があり実務未経験として入社することになります。
 

入社後の業務内容

当社のクラウド受付サービス「WowDesk」のWeb開発を担当することになりました。
最初は簡易的な機能開発や、React-Reduxの導入を実施しました。

www.wowdesk.jp

入社して感じた事

私が入社して感じた事を、苦戦した事とよかった事の2つの視点で記載していきます。 

<苦戦した事>

  1. 既存のコードが読めない
    これは知識不足で、コードの意味が全くわかりませんでした。実務未経験の最初の壁だと思います。例えば、私は基本JavaScriptを記述しているのですが、非同期処理が未知の世界でした。
    (今でも記述はしますが、100%分かるかと言われると怪しいです・・・)
    しかし、この辺りは先輩方が本当に優しくて、しっかりとフォローした頂きなんとか乗り越えていくことができました。

  2. 開発だけでなく、自社サービスの発展も視野に入れる
    開発作業だけでなく、チーム内でどういったニーズがある機能を追加するか等を議論していきます。ここの部分に関しては、前職にはなかった思考だったので今でも苦戦しています。
    単純に機能追加をするだけでなく、お客様の本質的な課題解決につながる機能をしっかりと議論していく必要があります。

  3. リモートワークのコミュニケーション
    私が入社した時には、コロナウイルスの真っ只中でした。ほとんどのメンバーがリモートワークを実施していました。
    一番苦戦したことは、コードに関する質問が気軽にできないということでした。併せて上記の知識不足の為、全く前に進めませんでした。そこで「Chrome リモートデスクトップ」を利用し、質問等がしやすくなりました。また、先輩のデバッグの仕方など直で確認できるので、取り入れて正解だったなと思います。

    support.google.com

     

<よかった事>

  1. 自由な働き方
    まずなんといっても私服!前職では毎日スーツを来ており、最初は私服がなじめませんでした。(田舎出身という事もあり、みんなおしゃれに見える・・・)
    リモートワークも早めに導入しており、ベンチャー企業らしさを感じる一面です。

  2. 開発環境が良い
    開発メンバーの椅子が最高です。個人では買うことがない椅子で業務を行うことできます。椅子だけではなく、開発PCのスペック等、エンジニアが開発する上で良い環境で業務を行うことができます。

  3. 開発メンバーが優しい ※最重要
    開発メンバーには本当に恵まれているなと感じています。実務未経験者の私でも丁寧にフォローしていただけています。
    ただ、何でも教えていただく訳でもなく、自分で考えるべき部分はしっかり指導していただけます。
    当社に入社や検討される方がいらしたらこの部分は、安心して頂ければと思います。
まとめ

ワウテックでエンジニアになりたいという方の参考になればと思っております。
また、技術的な投稿も今後は投稿していきますので、よろしくお願いいたします。

 

参考リンク

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

 

 

Reduxのパフォーマンス改善でImmutable Dataの概念にぶち当たった話

f:id:yamamoto5555:20200821113553j:plain

Web開発チームの山本です。

以前の記事でReduxのStore設計について書いた通り、Reduxの道へと踏み出したのですが、その後パフォーマンス面での課題にぶち当たったので、今回はReduxでパフォーマンス改善に取り組んだことについて紹介します。

背景

お客様によりWowTalkを使っていただくため社内の浸透をサポートする集計機能を実装しました(詳しくはこちら)。その中の最終アクティブでは、アカウントに所属するユーザーの最終アクティブを表示しています。この最終アクティブの画面の表示が、ユーザーの多いアカウントでは遅いという問題がありました。最終アクティブ画面以外にも影響が出ていたため、早急に改善する必要がありました。

f:id:yamamoto5555:20201006105315p:plain

 

課題

今回は、4000人以上のアカウントで最終アクティブのページの読み込みが遅いのが、顕著に現れたので、Google Dev Toolsを使用し計測してみました。計測方法としては、MacBook Proを使用し、4000人ほどのアカウントで試しました。最終的には、数万人のアカウントも使用できるようにすることが今回のゴールです。

 

・計測対象の操作

サイドバーから最終アクティブをクリックするが、表示の反応が遅れるのと、最終アクティブのリストが表示されるのが遅いです。

f:id:yamamoto5555:20201013103743g:plain

 

・合計のレンダリング時間

f:id:yamamoto5555:20201013101046p:plain

 

・APIのリクエストとレスポンス時間

f:id:yamamoto5555:20201013101108p:plain

 

Reduxの仕組み

Reactで安否確認機能というサービスも開発していますが、同じ規模のアカウントでユーザーの読み込みに時間に、これほど時間がかかることはなかったため、今回はRedux起因の問題であると予想を立てました。

 

redux-loggerで出力しているLogでは、明らかにリストとして表示しているユーザー情報をStoreに更新した時の処理に時間がかかっていました。デバックしてみると、4000人のユーザー情報でループ処理が実行されていました。調べてみたところ、Reduxのimmutabilityに関係していました。

 

ReduxとImmutable Data

Redux と React-Redux は、どちらもをshallow equality checkingを採用しています。shallow equality checking(またはreference equality)とは、2つの異なる変数が同じオブジェクトを参照しているかどうかをチェックします。 また、shallow equality checkingとよく比較されるのが、deep equality checking です。deep equality checkingは、2つのオブジェクトのプロパティのすべての値をチェックします。Immutable Dataに関しては、Reduxのドキュメントにも詳しく記載されています。

今回は、このshallow quality checkingによって、Storeに4000人のユーザー情報を入れる場合、それの差分を確認するためにループ処理が実行され、時間がかかっているのではと考えました。

 

改善内容

Storeに入れてしまうと、shallow quality checkingで時間がかかってしまうため、ユーザー情報は、Storeに入れないで、グローバル変数の中に入れることにしました。local stateに入れるのは、UIなどの管理は向いているかなと思ったのですが、今回のユーザー情報は、最初に取得するだけで、そのあとに更新などは必要ないため、グローバル変数に格納することになりました。

redux.js.org

 

また、その他の改善点としては、最終アクティブの画面ではソート機能やフィルター機能があるため、毎回数万規模で部門のフィルターで時間がかかってしまうため、最初の20件のみを表示するように変更しました。

 

改善結果

上記の改善施策やその他細々した修正をした結果、2秒かからないくらいになりました。

f:id:yamamoto5555:20201030104344p:plain

 

まとめ

Reduxのshallow equality checkingによって、データ量が多いものをStoreで管理しようとするとかなり時間がかかることが分かりました。サーバーの負荷を減らすためにも、今回はフロント側での処理で対応しました。

数万人までのアカウントで利用できるようにするのが目標でしたが、今後10万人近いアカウントにも対応が必要になる可能性もあります。その時は今のやり方では限界がくると思うので、再度修正が必要です(またその時考える)。

Reduxのパフォーマンス改善をする中でも、React部分の無駄な再レンダリングなど多々あり、まだまだ改善の余地がありそうです。これからも継続的に、改善していきたいです!

 

補足:

今回が初めてのパフォーマンス改善だったので、チューニングの基礎などが書かれていたWebフロントエンド ハイパフォーマンス チューニングという本を読みました。Reduxのパフォーマンス改善には直接関係していませんが、いろいろなテクニックが書かれており、今後にも開発にも役立ちそうでした。

 

Webフロントエンド ハイパフォーマンス チューニング
 

 

参考リンク 

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain

絵文字リアクションの開発裏話

f:id:yamamoto5555:20200911113722j:plain


Web開発チームの山本です。

WowTalkに絵文字リアクション機能がリリースされ、時間が少し立ってしまったのですが、自分の中でも思い出深い機能でもあるので、今回は絵文字リアクション機能の開発裏話を紹介します。

 

絵文字リアクション機能とは?

絵文字リアクション機能は、トーク上に投稿されたメッセージに対して12種類の絵文字で反応(リアクション)することができる機能です。メッセージに対してリアクションできるため、スタンプよりもカジュアルに使うことができます。詳しくは、こちらから!

 

なぜ絵文字リアクション機能を追加したのか?

日頃から多くのメッセージをWowTalkで行なっているため、スタンプがたくさん送信された際に、メッセージが流れてしまうという課題を感じていました。また、サービスコンセプトの1つでもある「感情」と絵文字リアクションとの親和性も高く、機能を追加する後押しとなりました。

 

開発で苦労したこと

機能開発時に大変だったことは、仕様の決定でした。リアクションの種類はもちろんのこと、何個まで選択できるか、リアクションした人を見せるかなど、想像以上に決めることが多く、技術部内でも何度も話し合いをしました。またWowTalkのヘビーユーザーである社内の人の声も聞きたかったので、共有のアンケート機能を使用して、リアクションの種類について社内からも意見を募っていました。面白かったのは、技術部内ではポジティブなリアクションだけでいいのではと話していたのですが、意外と社内の人からはリアクションの感情の幅があった方が良いと言う意見があったことです。確かに現在使用する中で、喜び以外の感情のリアクションを使用することがよくあるなと思っています。

 

個人的には、リアクションの追加・削除・更新などで起こるリアクション数の変更を適切に反映させることに、とても苦労しました。ただ追加するのに1リアクション増やす、削除するごとに1リアクション減らすでは、重複などのデータの矛盾が生じるため、メッセージが持つID(下のコードでいうと"messageId1"と"messageId2"のこと)をkeyにすることで、リアクションにアクセスしやすいようにしました。今回の実装で苦手に感じていた配列やオブジェクトの使い方にだいぶ慣れた気がします。

 

{
    messageId1: {
        clapping : {
            users :  [ user1,  user2, user3 ]
        },
        heart : {
            users: [ user2, user4 ]
        },    
    },
    messageId2: {
       rose : {
            users :  [ user4,  user5, user6 ]
        }
    }
}

 

 

リリース前のハプニング?!

テストも落ち着き、そろそろリリースだとなった時に、絵文字の入れ替えが発生しました。もともとは割れたハートと泣いている顔文字を入れる予定でしたが、上からのお達しで変更することになりました。そこで新しく入れる絵文字になったのが、👌と🌹です。バラは、もともと技術部でメンテナンス時などでお疲れ様という意味を込めて、バラのスタンプを送りあっていました。そのお疲れ様の意味を込めて、今回の絵文字に追加しました。皆さんも、ぜひ相手を労う時にたくさんバラを送ってください!

 

まとめ

嬉しいことに、社内からも「絵文字リアクション便利!」というポジティブな声を聞くと、頑張って開発して良かったなと思います。まだまだ改善の余地もあるとは思いますが、ユーザーの方にドンドン使っていただけると嬉しいです。これからも更にサービスを進化させられるよう、頑張っていきます!

 

 

我々ワウテック技術開発部はこのような環境で開発をしています。
興味を持った方がいらっしゃればいつでもご連絡下さい。

f:id:wowtech-dev:20190313163146j:plain