今天要做傳入todos,並且可以修改todo item,當修改的時候原本todo item會變成修改的input,裡面有一些小小tips喔!先執行npm run dev,然後一步一步做下去,就可以直接從瀏覽器上看到變化。
首先,我們要在app.js新增一個陣列todos:
const todos = [
{
task: 'Install packages',
isCompleted: false
},
{
task: 'Add webpack.config.js',
isCompleted: false
},
{
task: 'Break UI into components',
isCompleted: false
}
];
這個新增的todos還沒有傳入,class App裡面,所以我們要設定App的state初始值,還記得前面提到state初始化,我們寫在constructor中,並且需要super一下。
constructor(props) {
super(props);
this.state = {
todos
};
}
然後,App已經有todos存在state裡,我們必須把它傳給TodoList去render items:
<TodoList todos={this.state.todos} />
對TodoList來說,它會接收到props,可以在render function中, coonsole.log(this.props) 看看,會看到有傳入一個object,裡面有一個todos,長度為3的陣列。
而我們這邊要做的就是render items,建立另一個_renderItems function,通常非內建function可以做一點區別會比較好閱讀,我這邊是寫在所有內建function最下面(通常是render後面),並且名稱加上底線,可以建立自己的習慣就好。
// constructor習慣上寫在class裡面的第一個function位置
constructor(props) {
super(props);
this._renderItems = this._renderItems.bind(this);
}
// ...
// 在class最後面,render function之後加
_renderItems() {
const todos = this.props.todos;
let list = [];
todos.forEach((todo, idx) => {
list.push(<TodoItem key={idx} idx={idx} todo={todo} />);
});
return list;
}
這樣我們在TodoItem就可以收到props,這時的props只是單一task的內容
render() {
const todo = this.props.todo;
return (
<tr>
<td>{todo.task}</td>
<td>
<button>Edit</button>
<button>Delete</button>
</td>
</tr>
);
}
到目前的步驟,就可以看到一開始我們設定的todos陣列,每一個項目都正確的顯示在畫面中囉!
再來,我們要編輯單一task的內容。
因為我們要編輯task的時候,希望畫面改變成輸入框與加上儲存按鈕,畫面的變化都是由props或state來控制,這邊算是改變item的內部狀況,所以我們用一個內部的state變數來控制。並設定bind待會會用到的兩個新的function。
constructor(props) {
super(props);
this.state = {
isEditing: false
};
this._onEditClick = this._onEditClick.bind(this);
this._onCancelClick = this._onCancelClick.bind(this);
}
當isEditing為true的時候,顯示編輯的畫面:
render() {
const { todo, idx } = this.props;
if (this.state.isEditing) {
return (
<tr>
<td><input type="text" data-idx={idx} defaultValue={todo.task} /></td>
<td>
<button>Save</button>
<button onClick={this._onCancelClick}>Cancel</button>
</td>
</tr>
);
}
return (
<tr>
<td>{todo.task}</td>
<td>
<button onClick={this._onEditClick}>Edit</button>
<button>Delete</button>
</td>
</tr>
);
}
再來,當click Edit/Cancel時,切換isEditing的狀態,讓畫面依照state來render不同的狀態:
_onEditClick() {
this.setState({ isEditing: true });
}
_onCancelClick() {
this.setState({ isEditing: false });
}
目前做到這邊,已經可以顯示編輯畫面囉!用state來控制元件本身的狀態,是不是有點fu了呢~
最後,我們要來儲存user編輯的input內容,因為要改動的是props的內容,是整個資料內容,我們不能在component中改props,所以必須從最上層app.js改動,然後把改內容的function當成另一個props傳入。
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos
};
this._saveTask = this._saveTask.bind(this);
}
render() {
return (
<div>
<h1>React Todo List</h1>
<TodoAdd />
<TodoList todos={this.state.todos} saveTask={this._saveTask} />
</div>
);
}
_saveTask(idx, val) {
// copy array
let newTodos = [...this.state.todos];
// copy object
newTodos[idx] = Object.assign({}, newTodos[idx], { task: val });
this.setState({ todos: newTodos });
}
}
TodoList只需要把props的saveTask,再傳給TodoItem:
_renderItems() {
const { todos, saveTask } = this.props;
let list = [];
todos.forEach((todo, idx) => {
list.push(<TodoItem key={idx} idx={idx} todo={todo} saveTask={saveTask} />);
});
return list;
}
如同_onEditClick和_onCancelClick,我們要加一個_onSaveClick的function,並且先在constructor設定bind。
this._onSaveClick = this._onSaveClick.bind(this);
然後,給user填寫的input,我們必須給它一個ref,這是React裡面指定元件的名稱,我們可以使用this.refs.editInput得到這個元件。並且設定data-idx,把之前得到這個todo的索引值設定給input。並在Save button設定onClick。
render() {
const { todo, idx } = this.props;
if (this.state.isEditing) {
return (
<tr>
<td><input type="text" data-idx={idx} defaultValue={todo.task} ref="editInput" /></td>
<td>
<button onClick={this._onSaveClick}>Save</button>
<button onClick={this._onCancelClick}>Cancel</button>
</td>
</tr>
);
}
return (
<tr>
<td>{todo.task}</td>
<td>
<button onClick={this._onEditClick}>Edit</button>
<button>Delete</button>
</td>
</tr>
);
}
最後,新增_onSaveClick function,取得this.refs.editInput的data-idx屬性值與value,並傳給props的saveTask function,在getAttribute後用一個 + 把字串轉成數值。然後記得把isEditing state設定回false,這樣就完成儲存的動作。
_onSaveClick() {
const input = this.refs.editInput;
this.props.saveTask(+input.getAttribute('data-idx'), input.value);
this.setState({ isEditing: false });
}
今天的檔案已經放在Git上,到目前為止已經很清楚可以了解state和props是如何串起React元件的資料流了吧!