iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
0
Modern Web

React + GraphQL 全端練習筆記系列 第 9

仿Trello - 建立新增Todo功能

本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。

要新增Todo,就是要在KanBan上的lists資料添上一筆,所以新增的方法一起建在KanBan裡是最方便的。

// KanBan.jsx
export default function KanBan() {
  //...

  const [lists, updateLists] = useState(dummyData);

  //新增Todo方法
  function addTodo(listIndex, newTodo) {
    let newLists = [...lists];
    newLists[listIndex].todos.push({ name: newTodo, finished: false });

    updateLists(newLists);
  }

 // ...
 }

在新增的時候必須知道現在是在哪個list上新增,所以要傳遞listIndex給NewTodo。

連帶新增Todo方法,把index傳給List再傳給NewTodo。

// KanBan.jsx
export default function KanBan() {

  //...
  
  return (
    <span>
      <KanBanNav />
      <div fluid className="board p-1">
        {lists.map((list, index) => (
          // 傳 listId,addTodo 給 List
          <List key={index} {...list} listId={index} addTodo={addTodo} /> 
        ))}
      </div>
    </span>
  );
}
//List.jsx
export default function List({ title, todos, listId, addTodo }) {//解構賦值listId跟addTodo

    //...
    
    return(
    //...
        {showNew && (
        <NewTodo
          toggleShowNew={toggleShowNew}
          //傳給NewTodo
          listId={listId}
          addTodo={addTodo}
        />
        )}
    //...
    )

}

然後在NewTodo中運用傳進來的props新增Todo,不過因為有兩個事件onBlur,onInput都會執行新增、關閉輸入框的動作,所以先把整個流程包成一個方法。

export default function NewTodo({ listId, toggleShowNew, addTodo }) {//解構賦值

  //...

  function handleAddTodo(e) {
    if (e.target.value.trim()) {//去掉頭尾空白,是空字串的話就不執行新增
      addTodo(listId,  e.target.value );
    }

    e.target.value = "";
    toggleShowNew();
  }
  
  return (
    <Form>
      <Form.Control
        as="textarea"
        className="new-todo"
        style={textareaStyle}
        ref={newTodoRef}
        onBlur={handleAddTodo}   //新增事件
        onInput={autoResize}
        onKeyDown={(e) => {
          if (e.key === "Enter") {
            handleAddTodo(e); //新增事件
          }
        }}
      />
    </Form>
  );
}

到這邊就可以在輸入後,按ENTER或點擊離開輸入框的時候新增Todo了。

Composition與cloneElement

上面的流程中,listId跟addTodo傳給List後沒做任何事就傳給了NewTodo,顯得有點冗。

回頭寫這段的時候就想有沒有辦法跳過List直接把props傳給NewTodo?於是才發現了composition這個方法。

React中有些props是被預先定義的,像是props.children,這個props代表了在JSX中被寫在children位置的物件。

像是:

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

在FancyBorder中寫了一個message,這個部分就會做為 props.children帶給FancyBorder,接著在FancyBorder就能存取:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      <h1 className="Dialog-title">
        Welcome
      </h1>
      {props.children} //message被加在這
    </div>
  );
}

歸根究柢就是把JSX作為props傳給component,所以也可以定義自己的props,帶入JSX:

function App() {
  return (
    <SplitPane
      left={  //定義不同名稱的props
        <Contacts /> //帶入想要的JSX
      }
      right={
        <Chat />
      } />
  );
}

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left} //帶入的JSX顯示在這
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

用這個方式,把NewTodo在KanBan中就帶入,然後帶入listId跟addTodo:

//KanBan.jsx
  return (
    <span>
      <KanBanNav />
      <div fluid className="board p-1">
        {lists.map((list, index) => (
          <List
            key={index}
            {...list}
            //新增一個NewTodo的 prop
            NewTodo={<NewTodo listId={index} addTodo={addTodo} />} 
          />
        ))}
      </div>
    </span>
  );

到List中就能簡化成:

//List.jsx
export default function List({ title, todos, NewTodo }) {//解構NewTodo

    //...
    
 return (
    <div className="list p-2 m-1 rounded-lg">
      <div className="title">{title}</div>
      {todos.map((todo) => (
        <Todo key={todo.name} {...todo} />
      ))}
      {showNew && NewTodo} //從props取用NewTodo
      {!showNew && (
        <div className="footer d-flex">...
      )}
    </div>
  );
}

等等,原本從List傳給NewTodo的toggleShowNew方法要怎麼辦?

這要用另一個功能,React.cloneElement()。

這個功能可以複製元件後或添加/變更props,或變更children,在這裡用來增加新的props給NewTodo。

{showNew && React.cloneElement(NewTodo, { toggleShowNew })} //加入toggleShowNew

這樣就跟原本的功能一致了。

References:


上一篇
仿Trello - 建立新增Todo介面
下一篇
仿Trello - 建立編輯Todo介面與以下省略
系列文
React + GraphQL 全端練習筆記30

尚未有邦友留言

立即登入留言