Custom Hook 來自己寫一個 hook 吧!

| 2 min read

首先, 自訂的 Hook 一定要用 use 作為開頭

會用到的 hook 記得要 import 進來。 把變數 return 回去就可以在 app.js 中使用。

新增一個檔案叫做 useInput.js 把 App.js 裡面的邏輯抽出來

useInput.js

import { useState } from 'react'

export default function useInput() {
  const [value, setValue] = useState('')
  const handleChange = (e) => {
    setValue(e.target.value)
  }

  return {
    value,
    setValue,
    handleChange,
  }
}

再做一個把 todos 會用到的東西抽出來

useTodos.js

import { useState, useEffect, useRef } from 'react'
import useInput from './useInput'

function writeTodosToLocalStorage(todos) {
  window.localStorage.setItem('todos', JSON.stringify(todos))
}

export default function useTodos() {
  const id = useRef(1)
  const { value, setValue, handleChange } = useInput()
  const [todos, setTodos] = useState(() => {
    let todoData = window.localStorage.getItem('todos') || ''
    if (todoData) {
      todoData = JSON.parse(todoData)
      id.current = todoData[0].id + 1
    } else {
      todoData = []
    }
    return todoData
  })

  useEffect(() => {
    writeTodosToLocalStorage(todos)
  }, [todos])

  const handleButtonClick = () => {
    setTodos([
      {
        id: id.current,
        content: value,
        size: 'XL',
      },
      ...todos,
    ])
    setValue('')
    id.current++
  }

  const handleToggleIsDone = (id) => {
    setTodos(
      todos.map((todo) => {
        if (todo.id !== id) return todo // 不等於要修改的 id 直接回傳
        return {
          ...todo,
          isDone: !todo.isDone,
        } // 是要修改的 id 就把原本的 todo 加上要修改的屬性
      })
    )
  }

  const handleDeleteTodo = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id))
  }

  return {
    todos,
    setTodos,
    id,
    handleButtonClick,
    handleToggleIsDone,
    handleDeleteTodo,
    value,
    setValue,
    handleChange,
  }
}

如果要用同一個 hook 在新的 input 裡面使用,這樣寫就可以調用了 變成 ES6 object 的寫法

// 假如有第二個 input 也需要這個方法,就可以用:
const { value: todoName, setValue: setTodoName, handleChange: handleTodoName } = useInput()

最後 App.js 檔案就變得很乾淨了 import { useState, useRef, useEffect } from "react"; 也都不用寫在 App.js 裡面

App.js

import TodoItem from './TodoItem'
import useTodos from './useTodos'

function App() {
  const {
    todos,
    setTodos,
    id,
    handleButtonClick,
    handleToggleIsDone,
    handleDeleteTodo,
    value,
    setValue,
    handleChange,
  } = useTodos()

  return (
    <div className="App">
      <input type="text" value={value} onChange={handleChange} />
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo) => (
        <TodoItem
          key={todo.id}
          todo={todo}
          title={todo.title}
          size={todo.size}
          handleDeleteTodo={handleDeleteTodo}
          handleToggleIsDone={handleToggleIsDone}
        />
      ))}
    </div>
  )
}

export default App

這樣拆開來寫的好處是,介面跟邏輯分開,把共同的邏輯抽出來另外寫,這樣以後只要在套用一樣的邏輯,就可以產出不一樣的 UI 畫面了,是不是很棒呢~^^