【前言】
前幾天我們做的練習基本上都是放在同個Component檔案下,但事實上我們會切分Component,一來可以達到Component的可覆用性、可維護性、以及可讀性,所以今天我們就來試著切分Component改寫Todo List吧!
【正文】
下面是我們今天想要做的樣子:
這個TodoList有幾個功能:
1. 按Enter送出
2. 點擊Item會切換完成或是未完成
3. 每次送出清空輸入框
4. 如果是空字串不新增Todo Item
首先我們來切分這個TodoList,大致上會分成以下Component:
1. TodoList: 最外圍的Component,裡面包含TodoForm
和TodoItem
兩個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.js
、TodoForm.js
、TodoItem.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、跨組件的溝通了嗎?明天再來想想要分享什麼囉!