iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 9
0
自我挑戰組

請你當我的好朋友吧!ReactJS!系列 第 9

【DAY 09】利用狀態提升重新撰寫一次TodoList吧!

【前言】
  前幾天我們做的練習基本上都是放在同個Component檔案下,但事實上我們會切分Component,一來可以達到Component的可覆用性、可維護性、以及可讀性,所以今天我們就來試著切分Component改寫Todo List吧!

【正文】
  下面是我們今天想要做的樣子:
  TodoList
  這個TodoList有幾個功能:
  1. 按Enter送出
  2. 點擊Item會切換完成或是未完成
  3. 每次送出清空輸入框
  4. 如果是空字串不新增Todo Item

  首先我們來切分這個TodoList,大致上會分成以下Component:
  圖片
  1. TodoList: 最外圍的Component,裡面包含TodoFormTodoItem兩個Component,也是state所在處
  2. TodoForm: 輸入框,可以Submit新的Todo item
  3. TodoItem: 輸出目前的Todo list跟點擊item可以改變是否已完成

  現在我們做了拆分,你會發現這樣看起來有許多小Component都會跟我的Todolist相關,新增的時候要將新增的資料利用setState改變、要更動是否完成也是需要setState去完成。那這樣的情況,組件之間要怎麼互相溝通、更新state呢?這個時候就可以將state提升到共同組件,什麼意思呢?
  就是我們可以將state初始化在共同上層Component,再利用props傳給所有需要用到此state的子層Component。
  這樣子就可以達到跨組件溝通的效果囉,所以我們趕快怎麼實作他吧!
  1. 首先新增component資料夾下面的檔案:TodoList.jsTodoForm.jsTodoItem.js
  資料夾結構
  2. App.js

import React from 'react';
import TodoList from './component/TodoList';

// functional(stateless) Component寫法
// 如果此Component沒有要state或是其他生命週期方法可以這樣寫
const App = () => (
  <TodoList />
);

export default App;

  3. TodoList.js

import React from 'react';
import TodoForm from './TodoForm';
import TodoItem from './TodoItem';

class TodoList extends React.Component {
  constructor(props) {
		super(props);
			// 設定state
			// list裡面是個[],存放一個item物件
			// 物件格式如下
			// {
			//  	id,
			//    text,
			//    status,
			// }
      this.state = {
      list: [],
    };
	}
	// additem函式
	addItem = (text) => {
		const { list } = this.state;
		// input value要不為''才可以執行
        if (text !== '') {
           // Array.concat可以合併兩個陣列,並回傳一個新陣列物件
			const tempArr = list.concat({
				id: list.length + 1,
				text, // 如果你的key和value都一樣名字可以省略這樣寫
				status: false,
			});
			this.setState({
				list: tempArr,
			});
		}
	}
  // toggle item狀態函式
	toggleStatus = (id) => {
		const { list } = this.state;
		const tempArr = list.map(item => {
          // 比對是否有對應id
			if(item.id.toString() === id.toString()) {
				return ({
					id: item.id,
					text: item.text,
					status: !item.status,
				});
			}
			return item;
		});
		this.setState({
			list: tempArr,
		});
	}

	render() {
		const { list } = this.state;
		const divStyle = {
			width: '250px',
			margin: 'auto',
			textAlign: 'center',
		}; 
		return(
			<div style={divStyle}>
				<TodoForm onAddItem={this.addItem}  />
				<ul>
					{list.map(item => (
						<TodoItem
							key={item.id} // 一定要有
							id={item.id}
							status={item.status}
							onItemClick={this.toggleStatus}
						>
							{item.text}
						</TodoItem>
					))}
				</ul>
			</div>
		);
	}
}

export default TodoList;

  4. TodoForm.js

import React from 'react';

class TodoForm extends React.Component {
	constructor(props) {
		super(props);
		this.inputRef = React.createRef();
	}

	formSubmit = (e) => {
		const { onAddItem } = this.props;
		// 取消form submit原生的事件
		e.preventDefault();
		onAddItem(this.inputRef.current.value);
		// 再將input value設回空值
		this.inputRef.current.value = '';
	}

	render() {
		return(
			<form onSubmit={this.formSubmit}>
				<input type="text" name="todoItem" ref={this.inputRef} autoComplete="off" />
				<button type="submit" value="submit">submit</button>
			</form>
		);
	}
}

export default TodoForm;

  5. TodoList.js

import React from 'react';

class TodoItem extends React.Component {
	handleItemClick = (e) => {
		const { onItemClick } = this.props;
		onItemClick(e.target.id);
	}

	render() {
		const {
			children,
			id,
			status,
		} = this.props;
		
		return(
			<li
				id={id}
				onClick={this.handleItemClick}
				data-status={status}
				// 如果完成加上刪除線
				style={
                  status ? 
                  { textDecoration: 'line-through' } : 
                  { textDecoration: 'none' }
                }
			>
				{children}
			</li>
		);
	}
}

export default TodoItem;

  這樣就會跟上面畫面實作的效果一樣囉,這樣有更清楚props、state、refs、跨組件的溝通了嗎?明天再來想想要分享什麼囉!


上一篇
【DAY 08】React Component也有生老病死?(下)
下一篇
【DAY 10】這樣我們關係有比較近了嗎?ReactJS
系列文
請你當我的好朋友吧!ReactJS!30

尚未有邦友留言

立即登入留言