日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React + TS 完成 TodoList

作者:拯救世界的光太郎 更新時間: 2022-02-17 編程語言

本文屬于代碼分析,而非從0到1的代碼書寫。

本文會將每一段重要代碼的含義都分析清除,然后在代碼后面通過設定一些小問題,考驗一下大家是否對 “各種語句” 以及 “不同場景所需邏輯” 的使用已經掌握牢固。


? ?Redux

借助TS的Hooks操作數據,可以說是非常的簡便了,但是如果想要靈活地使用Hooks,了解React的Redux依舊是重中之

  • 組件有各種行為,例如:增、刪、改、查。
  • 組件不允許直接修改state,只能先創建出對應的action,里面記錄著行為的名稱和變化的數據,然后借助dispatch交給store。
  • store將改變前的state和action交給reducer,reducer去操作具體的狀態,然后將新的state再存儲到store中,store將操作完的state再交給組件。

這就是redux的工作流程,或者是工作機制。


? ?整體結構

根據項目的結構,將項目分為上下兩個組件,上方是輸入組件Input,下方是展示組件List

  • TodoList
    • Input
    • List

? ?src目錄結構

我們就根據代碼的引入順序,由外至內一步一步地分析關鍵的文件。

? ?App.tsx

// App.tsx

import React from 'react';

import TodoList from './components/TodoList';

function App() {
  return (
    <div className="App">
      <TodoList />
    </div>
  );
}

export default App;

結構十分簡單,只需要引入TodoList,然后使用這個組件就可以。

? ?TodoList.tsx

import React, { FC, ReactElement, useCallback, useEffect, useReducer } from "react";
import TdInput from './Input';
import TdList from "./List";
import { todoListReducer } from "./reducer";
import { ACTION_TYPES, ITodoList, ITodo } from "./typings";

function initTL(initTodoList: ITodo[]): ITodoList{
  return {
    todoList: initTodoList
  }
}
// 狀態惰性初始化,使用該方式,會等到useRuducer執行之后才去創建初始化的數據


const TodoList:FC = ():ReactElement=>{ 

  const [state, dispatch] = useReducer(todoListReducer, [], initTL);  
  // 這里我們不使用useState去創建state,一方面是因為:對該數據的大量操作都位于子組件中,而非供自己使用
  // 另一方面:是因為todoList中的數據涉及到增、刪、改,對數據的操作相對復雜,用useState無法對數據的深度修改進行優化


  useEffect(() => {
    const todoList = JSON.parse(localStorage.getItem('todolist') || '[]');

    dispatch({
      type: ACTION_TYPES.UPDATE_TODOLIST,
      data: todoList
    })
    // dispatch中有我們的行為,以及對應處理的數據
  }, [])
  // 這個useEffect由于不對任何數據進行檢測,所以只會在頁面剛加載時執行一次。這個時候,我們需要從本地獲取數據


  useEffect(() => {
    localStorage.setItem('todolist', JSON.stringify(state.todoList));
  }, [state.todoList])
  // 當todoList發生變化時,向localStorage中保存最新的數據


  const addTodo = useCallback((todo: ITodo)=>{
    dispatch({
      type: ACTION_TYPES.ADD_TODO,  // 行為
      data: todo  // 修改的數據
    })
    // 當子組件觸發addTodo,需要向todoList中添加數據時,只需要告訴dispatch行為是添加,并把添加的數據放進去即可
  }, [])
  // 一個方法,如果使用者不是自己,而是供子組件使用,最好是用useCallback將其包裹起來【第二個參數為依賴】


  const toggleTodo = useCallback((id: number)=>{
    dispatch({
      type: ACTION_TYPES.TOGGLE_TODO,
      data: id
    })
  }, [])

  const removeTodo = useCallback((id: number)=>{
    dispatch({
      type: ACTION_TYPES.REMOVE_TODO,
      data: id
    })
  }, [])


  return (
    <div className="todolist">
      <h1>TodoList</h1>

      <!-- 將操作數據的方法和數據源交給子元素,由子元素決定在什么時候去添加元素 -->
      <TdInput 
        addTodo={addTodo}  
        todoList={state.todoList}
      /> 

      <br />

      <!-- 在List中,包含了切換每一個item完成狀態、刪除item的操作,因此我們需要將兩個方法和數據源都傳遞過去 -->
      <TdList
        todoList={state.todoList}
        toggleTodo={toggleTodo}
        removeTodo={removeTodo}
      />
    </div>
  );
}

export default TodoList;

在這個組件中,我們需要弄明白6個核心要點:

  1. 什么是狀態惰性初始化?為什么要對初始化的狀態做惰性初始化處理?
  2. 什么時候用useState創建狀態?什么時候用useReducer創建狀態?
  3. useEffect依賴為空時,什么時候被觸發?依賴非空時,什么時候被觸發?
  4. 什么時候將數據存儲到localStorage中?什么時候取出來?
  5. dispatch里面傳了什么?它又在做什么?
  6. 我們需要向組件中傳遞什么?

? ?reducer.ts

import { ACTION_TYPES, IAction, ITodoList, ITodo } from "./typings";

function todoListReducer(preTodoList: ITodoList, action: IAction): ITodoList{
// 在dispatch中包裝的action,其實就是傳遞到了這里,所以這里的action包含了兩個屬性:type、data
// 這個preTodoList,是不需要我們主動傳遞一個實參和該形參對應的,因為實際并不是我們在調用的這個reducer,在reducer被調用時,會自動傳入一個原始的state,作為變化前的初始狀態


  const {type, data} = action; // 取出行為的類型、修改的數據

  // 根據不同的行為,對數據做不同的更改
  switch(type){
    case ACTION_TYPES.ADD_TODO:
      return  {
        todoList: [...preTodoList.todoList, data as ITodo ]  // 返回處理后的數據(原數據+新增的數據)
      }
    case ACTION_TYPES.REMOVE_TODO:
      return {
        todoList: preTodoList.todoList.filter(todo => todo.id !== data)  // 過濾出與被刪除的item不同id的項
      }
    case ACTION_TYPES.TOGGLE_TODO:
      return {
        todoList: preTodoList.todoList.map(todo => {  // 將被點擊的項的isCompleted取反,其他的項直接返回
          return todo.id === data ?
          {
            ...todo,
            isCompleted: !todo.isCompleted
          }:{
            ...todo
          }
        })
      }
    case ACTION_TYPES.UPDATE_TODOLIST:
      return {
        todoList: data as ITodo[]  // 此時是頁面初始化,只需要將從localStorage中取出的數據裝入到store中即可
      }
    default:
      return preTodoList;
  }
}

export {
  todoListReducer
}

這里需要明白4個核心

  1. todoListReducer在什么時候被調用?
  2. 我們有傳遞參數和preTodoList相對應嗎?
  3. action是哪里傳遞來的參數?它里面都有什么?
  4. 我們根據不同的行為,需要返回什么數據?是返回處理的單條數據todo,還是返回完整的數據TodoList?

? ?TdInput.tsx

import React, {useRef, FC, ReactElement} from "react";
import { ITodo } from "../typings";

// 接口:用來控制參數的種類和個數
interface IInputProps {
  addTodo: (todo: ITodo) => void,
  todoList: ITodo[]
};

const TdInput: FC<IInputProps> = ({
  addTodo,
  todoList
  // 結構賦值,從父組件中傳遞來的參數其實都存儲在一個對象中
}): ReactElement =>{ 

  const inputRef = useRef<HTMLInputElement>(null);
  // ref可以用來標注標簽

  const addItem = (): void=>{
    const val: string = inputRef.current!.value.trim();
    // "!" 為我們增加的斷言,表示這里一定可以拿到數據
    const isExist = todoList.find(todo => todo.content === val);
    if(isExist){
      alert("該項已存在!");
      return;
    }

    addTodo({
      id: new Date().getTime(),
      content: val,
      isCompleted: false
    })
    // 在我們判定需要調用添加數據后,就可以調用addTodo了,如何傳參需要參考父組件,因為這個函數來自父組件,所以只有看了父組件是如何定義的,才能夠知道如何使用。
    // 因為這個函數的行為是確定的,就是添加元素,所以我們就只需要在這里定義一個元素,然后作為函數的參數去調用函數就可以了。函數在定義時,也是這么設定的,只需要傳遞一個參數

    inputRef.current!.value = '';
    // 傳遞完參數,一定不能忘了將輸入框置空
  }

  return (
    <div className="todo-input">
      <input type="text" ref={inputRef}/> &nbsp;
      <button onClick={addItem}>button</button>
    </div>
  )
}

export default TdInput; 

這里面有5個關鍵點:

  1. 接口是用來限制誰的?
  2. 從父組件中傳遞過來的參數是一個對象?還是一個參數列表?
  3. 如何用ref來標注一個標簽?
  4. xxx.yyy.zzz,yyy一定有值,可是還是會提示yyy可能為undefined怎么辦?
  5. 從父組件傳遞過來的addTodo,我們需要向這個函數傳遞什么參數?

? ?TdList.tsx

import React, { FC } from "react";
import { ITodo } from "../typings";
import TdItem from "./Item";

interface IListProps{
  todoList: ITodo[],
  toggleTodo: (id: number) => void
  removeTodo: (id: number) => void,
}

const TdList: FC<IListProps> = ({
  todoList,
  toggleTodo,
  removeTodo
}) => {
  return (
    <div className="todo-list">
      {
        // ↓ 表示todoList 存在的話
        todoList && todoList.map((todo: ITodo)=>{
          return (
            <TdItem 
              key={ todo.id }
              todo = {todo}
              toggleTodo={ toggleTodo }
              removeTodo={ removeTodo }
            />
          )
        })
      }
    </div>
  )
}

export default TdList;

這里就十分簡單了,因為我們將List又分為了一個個小的Item組件,所以List組件并沒有做太多事情。只是將數據進行簡單的遍歷,拿到一個個的數據todo,然后將這些數據再傳遞給TdItem組件,由該組件將一個個的數據創建成一個個Item組件。

但是這里我們一定不能忘了將TodoList組件傳遞來的toggleTodo、removeTodo方法傳遞給子組件,因為我們現在是在子組件上去調整對應的完成、刪除狀態的

? ?TdList.tsx

import React, { FC, ReactElement } from "react";
import { ITodo } from "../typings";

interface IItemProps{
  todo: ITodo,
  toggleTodo: (id: number) => void
  removeTodo: (id: number) => void
}

const TdItem:FC<IItemProps> = ({
  todo,
  toggleTodo,
  removeTodo
}):ReactElement => {

  const {id, content, isCompleted} = todo

  return (
    <div className="todo-item">
      <!--  
          在點擊方框時,需要改變todo的isComplete屬性
          當isComplete為true時,就表示已完成,對應checked自然就是true,這個時候方框就是一個被勾選的狀態
      -->
      <input 
        type="checkbox" 
        onChange={ ()=>toggleTodo(id) }
        checked={ isCompleted }
      /> &nbsp;

      <!-- 我們需要為已經完成的todo添加一個刪除線 -->
      <span 
        style={ { textDecoration: isCompleted ? 'line-through' : 'none' } }
      >{content}</span> &nbsp;

      <!-- 當我們點擊刪除時,只需要刪除掉對應id的todo即可 -->
      <button
        onClick={ () => removeTodo(id) }
      >刪除</button>
    </div>
  )
}

export default TdItem;

這里我們需要明白2個關鍵點:

  1. 如何讓我們的點擊,反饋到方框中?
  2. 如何讓我們的點擊,改變todo中的數據?

原文鏈接:https://blog.csdn.net/qq_44647809/article/details/121202763

欄目分類
最近更新