Open6
Electron x Next.js でTodoアプリを作る

プロジェクトを作る
$ npx create-next-app --example with-electron-typescript todoapp
$ cd todoapp
# 初期状態だとなぜか `electron-is-dev` の型定義が無いと言われてしまったので、インストールし直す……
$ npm install
# 起動
$ npm run dev
tsconfig.jsonの設定
(この節は実は不要だった。コメント参照)
参考: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
$ npm install --save-dev @tsconfig/node12
// tsconfig.json
{
"extends": "@tsconfig/node12/tsconfig.json",
"compilerOptions": {
"preserveConstEnums": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
TailWind CSSの利用
参考: https://tailwindcss.com/docs/guides/nextjs
参考: https://zenn.dev/matamatanot/articles/e0e6371fe28b7479fb3f
$ npm install tailwindcss@latest postcss@latest autoprefixer@latest
$ npx tailwindcss init -p
// tailwind.conf.js
module.exports = {
purge: ["./pages/**/*.js", "./components/**/*.js"],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
// _app.js
import "tailwindcss/tailwind.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;

tsconfig.json
はNext.JSとElectronそれぞれにデフォルトで存在するので、新たに作る必要はなかった

まずはUIのガワだけ作っていく。
コンポーネントはこちらを使わせていただく
// components/Container.tsx
import TodoList from "./TodoList";
const Container = () => (
<div
className="
h-100
w-full
flex
items-center
justify-center
bg-teal-lightest
font-sans
"
>
<TodoList />
</div>
);
export default Container;
// components/TodoList.tsx
import TodoItem from './TodoItem'
const TodoList = () => (
<div className="bg-white rounded shadow p-6 m-4 w-full lg:w-3/4 lg:max-w-lg">
<div className="mb-4">
<h1 className="text-grey-darkest">Todo List</h1>
<div className="flex mt-4">
<input className="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-grey-darker" placeholder="Add Todo" />
<button className="flex-no-shrink p-2 border-2 rounded text-teal border-teal hover:text-white hover:bg-teal">Add</button>
</div>
</div>
<div>
<TodoItem />
</div>
</div>
)
export default TodoList
// components/TodoList.tsx
const TodoItem = () => (
<div className="flex mb-4 items-center">
<p className="w-full text-grey-darkest">
Add another component to Tailwind Components
</p>
<button className="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-green border-green hover:bg-green">
Done
</button>
<button className="flex-no-shrink p-2 ml-2 border-2 rounded text-red border-red hover:text-white hover:bg-red">
Remove
</button>
</div>
);
export default TodoItem
// pages/index.tsx
import Container from '../components/Container'
const IndexPage = () => {
return (
<Container/>
)
}
export default IndexPage
この状態で npm run dev
すると、こんな感じのUIが出来る。
当たり前だが、まだロジックを書いてないので、何も動かない。

// components/TodoList.tsx
import { useState } from 'react'
import TodoItem from './TodoItem'
import { v4 as uuidv4 } from "uuid";
function TodoList() {
const [tasks, setTasks] = useState([
{key: uuidv4(), name: 'サンプルだよ'}
])
const [name, setName] = useState('')
const handleChange = (e) => {
setName(e.target.value)
}
const addTask = () => {
const task = {
key: uuidv4(),
name
}
setTasks([
...tasks,
task
])
}
const removeTask = (key: string) => {
setTasks(tasks.filter(task => task.key !== key))
}
const taskList = () => tasks.map(task => (
<TodoItem
key={task.key}
task={task}
removeTask={removeTask}
/>
))
return (
<div className="bg-white rounded shadow p-6 m-4 w-full lg:w-3/4 lg:max-w-lg">
<div className="mb-4">
<h1 className="text-grey-darkest">Todo List</h1>
<div className="flex mt-4">
<input onChange={handleChange} className="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-grey-darker" placeholder="Add Todo" />
<button onClick={addTask} className="flex-no-shrink p-2 border-2 rounded text-teal border-teal hover:text-white hover:bg-teal">Add</button>
</div>
</div>
<div>
{taskList()}
</div>
</div>
)
}
export default TodoList
// TodoItem.tsx
import { Task } from '../interfaces/Task'
type Props = {
task: Task
removeTask: any
}
const TodoItem = ({ task, removeTask}: Props) => (
<div className="flex mb-4 items-center">
<p className="w-full text-grey-darkest">
{task.name}
</p>
<button className="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-green border-green hover:bg-green">
Done
</button>
<button onClick={() => removeTask(task.key)}className="flex-no-shrink p-2 ml-2 border-2 rounded text-red border-red hover:text-white hover:bg-red">
Remove
</button>
</div>
);
export default TodoItem
タスクの追加と削除が出来るようになった。
- めんどくさがってreduxを使わないでやってしまったけど、逆にめんどくさくなってしまった
- TypeScriptが全く使いこなせていない

if (isDev) {
mainWindow.webContents.openDevTools();
}
これをElectron側の index.ts
に入れておくと開発中のみDevToolsが出て便利