iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 26
0

昨天將 React Todo 的殼寫出來了,今天就把之前做的 Firebase 當作心臟給放進去,讓它活起來吧!

事前準備

  1. 安裝 firebase
  2. yarn add firebase
  3. 將 service/firebase cp 到 src 底下
  4. 開啟 dev server => yarn start

來略懂略懂 Life Cycle

Dan Abramov <- react 帥哥 在 twitter 分享他畫的 lifecycle 圖

lifecycle

最左邊是 Mounting,就是 react 第一次做 component 的時候會經過的地方

在結束的時候會呼叫 componentDidMount 這個 method

官方文件有說在這邊開發者可以呼叫 setState 來進行更新

只要 setState 被呼叫(或收到新的 props)就會進入圖上的 updating 狀態

使用這幾個重點就可以給予 TodoList 生命了!

取得列表

使用 state 和 componentDidMount

class

state = {
  list: [],
  isLoading: false,
}
componentDidMount() {
  // 將狀態更新成 isLoading
  this.setState({
    isLoading: true,
  })
  FirebaseService.getTodoList()
    .then((list) => {
      // 將讀取的列表放進更新裡!然後將 isLoading 改為 false
      this.setState({
        list,
        isLoading: false,
      })
    });
}

render function 改成

render() {
  const {
    list,
    isLoading,
  } = this.state;

  return (
    <div className="container">
      <ActionController
        handleAdd={this.handleAdd}
      />
      <LoadingSpinner isLoading={isLoading} />{/* 是否讀取中 */}
      <TodoList
        list={list}
        handleUpdate={this.handleUpdate}
        handleDelete={this.handleDelete}
      />
    </div>
  )
}

在 class component 裡面新增了

  • this.handleUpdate
  • this.handleDelete
  • this.handleAdd

這三個 function 來控制更新刪除與新增,分別帶入 TodoList 和 ActionController 元件

更新

handleUpdate = (id, item) => {
    this.setState({
      isLoading: true,
    })
    FirebaseService.updateTodo(id, item)
      .then((res) => {
        // PureComponent will not work!
        this.setState(({list}) => {
          return {
            list,
            isLoading: false,
          }
        })
      })
  }

可以使用 setState 來讓底下進行更新,但是這樣寫會讓 PureComponent 不能進行更新

因為 PureComponent 會在 shouldComponentUpdate 的時候做 props 的 shallow check 然後如果有變動就會進行 Component 的更新

意思是僅檢查物件的 reference 不會檢查物件裡的 property 的變動

所以像是

group = {
  a: 'jerry',
  b: 'taiming'
}
groupX = group
groupY = group

groupX.c = 'odie'

groupX === groupY // true

會是 true 因為都指向同一個 reference

這樣做 react 可以很快地知道哪個節點需要更新哪些不用更新

所以可以改成

this.setState(({list}) => {
  return {
    isLoading: false,
    list: list.map((item) => {
      if (item.id === id) {
        return res;
      }
      return item;
    }),
  }
})

刪除

handleDelete = (id) => {
  this.setState({
    isLoading: true,
  })
  FirebaseService.deleteTodo(id)
    .then((res) => {
      this.setState(({ list }) => {
        return {
          isLoading: false,
          list: list.filter((item) => item.id !== id),
        }
      })
    }) 
}

新增

handleAdd = (name) => {
  this.setState({
    isLoading: true,
  })
  FirebaseService.addTodo(name, false)
    .then((res) => {
      this.setState(({ list }) => ({
        isLoading: false,
        list: [...list, res],
      }))
    })
}

列表 Component

class TodoList extends React.Component {
  render() {
    const {
      list,
      handleUpdate,
      handleDelete,
    } = this.props;

    return (
      <ul>
        {
          list.map((item) => (
            <TodoItem
              key={item.id}
              item={item}
              handleUpdate={handleUpdate}
              handleDelete={handleDelete}
            />
          ))
        }
      </ul>
    )
  }
}

Item Component

const getCheckedClass = (checked) =>  checked ? 'list-group-item-dark' : '';
class TodoItem extends React.Component {
  handleOnClick = (evt) => {
    const {
      item,
      handleUpdate,
    } = this.props;
    item.check = !item.check;
    handleUpdate(item.id, item);
  }

  handleOnDelete = (evt) => {
    const {
      item,
      handleDelete,
    } = this.props;

    // 記得這個因為上層有更新的 code 所以用這個避免刪除時冒泡上去被更新
    evt.stopPropagation();
    handleDelete(item.id);
  }
  render() {
    const {
      item,
    } = this.props;
    return (
      <li
        className={`list-group-item list-group-item-action ${getCheckedClass(item.check)}`}
        onClick={this.handleOnClick}
      >
        <span className="todo-content">{item.name}</span>
        <span className="close" onClick={this.handleOnDelete}>x</span>
      </li>
    );
  }
}

最後是新增

class ActionController extends React.Component {
  inputNode = null;
  inputRefSetup = (inputNode) => {
    this.inputNode = inputNode
  }

  handleOnCreateClick = (evt) => {
    const {
      handleAdd,
    } = this.props;

    handleAdd(this.inputNode.value)
    evt.preventDefault();
  }

  render() {
    return (
      <form className="form-inline mt-3 mb-3">
        <div className="form-group">
          <label className="sr-only">Todo</label>
          <input
            className="form-control"
            ref={this.inputRefSetup}
            type="text"
            id="todo-input" placeholder="待辦事項"
          ></input>
        </div>
        <button
          className="btn btn-primary ml-3"
          id="addTodo"
          onClick={this.handleOnCreateClick}
        >新增</button>
      </form>
    )
  }
}

新增的地方我把 input 節點拿出來,在 createClick 的時候把 value 丟給前面處理

今天快速的把 react 部分過完,發現使用元件的概念來寫前端,邏輯處理變得簡單得多了

另外加上了 loading 提示。現在處理 side effect 也是比之前容易得多 了!


今天的 code


上一篇
Day25 - 使用 React 來解決問題 [part2]
下一篇
Day27 - 使用 React-Redux 來管理狀態 [part1]
系列文
認真學前端開發 - 以 TodoList 為例30

尚未有邦友留言

立即登入留言