debbygigigi / notes

My notes for any technologies of Web, Tech, etc.
9 stars 1 forks source link

[學習筆記] React todo 流水帳 #15

Open debbygigigi opened 5 years ago

debbygigigi commented 5 years ago

建立專案:create-react-app

npx create-react-app react-todolist

create-react-app 算是官方做好的一個懶人包,一鍵完成所有環境建置 功能相當於 vue-cli

yarn start

就會編譯完成並在預設 localhost:3000 開啟 server,並且是儲存直接 hot-reload

建立 Component

先建立 TodoList.js ,這是我們 TodoList 主要的 component 在 App.js 引入,render 裡也換上上 TodoList 的 tag

import TodoList from './TodoList';
...
class App extends Component {
  render() {
    return (
      <div className="App">
        <TodoList />
      </div>
    );
  }
}

TodoList.js

import React, { Component } from 'react' // component 必須有這一行

class TodoList extends Component {
    render() {
        return(
            <div className="todo-list">
                <h1>Todo List</h1>
            </div>
        )
    }
}

export default TodoList;

display todos

state

class App extends Component {
  state = {
    todos: [
        {
            id: 1,
            title: 'learn React',
            completed: false
        },
        {
            id: 2,
            title: 'make react todo app',
            completed: false
        },
        {
            id: 3,
            title: 'eat eat eat',
            completed: false
        }
    ]
}
  render() {
    return (
      <div className="App">
        <TodoList />
      </div>
    );
  }
}

React-devtool https://github.com/facebook/react-devtools

2019-02-12 10 48 25

props

 <TodoList todos={this.state.todos}/>

換成vue的寫法為

 <TodoList :todos="state.todos"/>

將 todos 印出來

return this.props.todos.map((todo) => (
            <h3>{ todo.title }</h3>
        ))

建立 TodoItem

TodoItem.js

export class TodoItem extends Component {
  render() {
    return (
      <h3>{ this.props.todo.title }</h3>
    )
  }
}

TodoList.js

class TodoList extends Component {
    render() {
        return this.props.todos.map((todo) => (
            <TodoItem key={todo.id} todo={todo} />
        ))
    }
}

propTypes

props的規範

在 TodoList 下定義自己的props,要先 import PropTypes

import PropTypes from 'prop-types';
...
TodoList.propTypes = {
    todos: PropTypes.array.isRequired
}

覺得 vue 比較直覺一點

export default {
    props: {
        todos: {
            type: Array,
            required: true
        }
    }
}

如果不強制type和required,也可以直接用array方式列出: props: ['todos']

inline style

style要用兩個大括弧,css 要使用 Camel case

<div style={{ backgroundColor: '#eee' }}>
    <h3>{ this.props.todo.title }</h3>
</div>

與 vue 的對比 (參考:https://vuejs.org/v2/guide/class-and-style.html#Object-Syntax-1)

<div :style="{backgroundColor: '#eee' }">
</div>

第二種方式:建 variable

<div style={itemStyle}>
        <p>{ this.props.todo.title }</p>
      </div>
const itemStyle = {
  backgroundColor: '#eee'
}

第三種:function

export class TodoItem extends Component {
  getStyle = () => {
    if (this.props.todo.completed) {
      return {
        textDecoration: 'line-through'
      }
    } else {
      return {
        textDecoration: 'none'
      }
    }
  }
  render() {
    return (
      <div style={this.getStyle()}>
        <p>{ this.props.todo.title }</p>
      </div>
    )
  }
}

改變狀態:setState

TodoItem.js

const {id, title} = this.props.todo;
<input type="checkbox" onChange={this.props.markComplete.bind(this, id)}/>

當 checkbox 改變時 call function,但這個 function 必須放在 state 有 todos 那層,並透過 props 傳遞 function(vue 不是用props 傳遞function,而是子層 emit 事件傳遞給父層) 注意這裏的 function 要夾帶變數的話要用 bind,第一參數必須是 this,第二參數才是要傳遞的變數

app.js

markComplete = (id) => {
    this.setState({
      todos: this.state.todos.map(todo => {
        if(id === todo.id) {
          todo.completed = !todo.completed
        }
        return todo;
      })
    })
  }

利用 setState 改變 state

雙向綁定

vue 很簡單,只要用 v-model 即可

<input type="text" v-model="title"></input>
...
data() {
    return {
        title: ''
    }
}

react 是怎麼實現呢

state = {
    title: ''
  }

  onChange = (e) => {
    this.setState({title: e.target.value})
  }
...
<Input placeholder="Add Todo..." name="title" style={{width: '100%'}}
            value={this.state.title} onChange={this.onChange}/>

這樣就完成雙向綁定了

接下來要把新的 Todo 新增到 Todos 陣列

<Button type="submit" onClick={this.onSubmit}>submit</Button>

先建立 submit 事件

onSubmit = (e) => {
    e.preventDefault(); // 不執行原生的 submit 事件
    this.props.addTodo(this.state.title);
    this.setState = {
      title: ''
    }
  }

一樣執行從 App.js props 下來的 addTodo 事件

App.js

addTodo = (title) => {
    const newTodo = {
      id: 4,
      title,
      completed: false
    }
    this.setState({
      todos: [...this.state.todos, newTodo]
    })
  }

uuid

這邊就有個問題了,newTodo 的 id 要怎麼給呢? 不能給固定的,因為 id 不能重複 這時就要找一個可以製造 id 的工具「node-uuid」https://github.com/kelektiv/node-uuid 選擇版本4就能產生隨機的 id 將所有用到 id 的地方直接用 uuid.v4() 取代

import uuid from 'uuid';
...
const newTodo = {
      id: uuid.v4(),
      title,
      completed: false
    }

Router

接下來 下載 react-router-dom https://github.com/ReactTraining/react-router/tree/master/packages/react-router-dom

import React from 'react'

export default function About() {
  return (
    <React.Fragment>
      <h1>About</h1>
      <p>This is a todo app by React.</p>
    </React.Fragment>
  )
}

React.Fragment 應該就像 Vue 裡的 template ,是一個虛擬的 DOM 節點

引用 react-router-dom import { BrowserRouter as Router, Route } from 'react-router-dom'

<Router>
        <div className="App">
          <Header />
          <Route exact path="/" render={props => (
            <React.Fragment>
              <AddTodo />
              <TodoList todos={this.state.todos} markComplete={this.markComplete}
                deleteTodo={this.deleteTodo} addTodo={this.addTodo}/>
            </React.Fragment>
          )} />
          <Route path="/about" component={About} />
        </div>
      </Router>

串 api

接下來要串 api 用 json placeholder 來串假資料 https://jsonplaceholder.typicode.com/todos

用 axios https://github.com/axios/axios

取得資料 get

componentDidMount() {
    Axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
    .then(res => this.setState({todos: res.data}))
  }
...
state = {
    todos: []
  }

componentDidMount 就是 vue 裡的 mounted componentWillMount 就是 vue 裡的 created 試過將 axios get api 這段放在 componentWillMount 裡也是可以的(跟 vue 裡可以放在 created 裡一樣)

post 資料

接著,我們要透過 post 新增 todo

addTodo = (title) => {
    Axios.post('https://jsonplaceholder.typicode.com/todos', {
      title,
      completed: false
    })
      .then(res => this.setState({
        todos: [...this.state.todos, res.data]
      }))
  }

deploy 部署

最後就是要部署了 參考 https://codeburst.io/deploy-react-to-github-pages-to-create-an-amazing-website-42d8b09cd4d 部署至 github-pages

部署是成功了,但是遇到了一個問題 https://medium.com/@Dragonza/react-router-problem-with-gh-pages-c93a5e243819