(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操
转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
1 父组件向子组件传递数据
紧接上一篇的代码,我们实现了 TodoList 的相关功能。针对这个超级简单的页面,我们依然能够用“组件”化的思维进行改写。
❓拆分组件时,我们应该是一个怎样的思考和实践过程呢? 答:
1️⃣查看项目的 index.js
文件,这个文件是项目的“入口文件”。在这个文件里,我们清楚地看到:
即,我们在入口文件里,仅把 TodoList
这个大“组件”挂载到 id 为 root
的元素上。
2️⃣既然仅仅挂载一个 TodoList
组件,即表示:最终页面展示的所有内容,都在 TodoList
这一个组件之中。
3️⃣最外层的“大组件”我们知道是谁了,接下来就可以对这个“大组件”进行拆分:
4️⃣“组件”拆分好后,我们在 src
目录下新增一个文件 TodoItem.js
:
5️⃣按照之前学习的知识,我们将 TodoItem
这个“组件”的一些初始化的代码写上(你也动起手来,我们一起写写):
import React, { Component } from "react";
class TodoItem extends Component {
render() {
return(
<div>
我是小组件 TodoItem 里的初始测试数据!
</div>
)
}
}
export default TodoItem;
6️⃣ TodoItem
这个组件倒是创建好了,它该放在哪里呢,或者说谁会用它呢?
答: TodoList
这个大组件需要用。
6️⃣-①:打开 TodoList.js
这个文件;
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem"; // 6️⃣-②:在这里将 TodoItem 这个小组件引用进来以便使用;
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>
提交
</button>
</div>
<ul>
{
this.state.list.map((item, index)=>{
/*
6️⃣-③:将之前循环 list 数据项时创建的 li 标签注释掉。
因为我们现在需要通过“组件”的形式来编写代码,而这部分内容都定义在
组件 TodoItem 里;
<li
key={index}
onClick={this.handleItemDelete.bind(this, index)}
dangerouslySetInnerHTML={{__html: item}}
>
</li>
*/
return(
/*
6️⃣-④:取而代之,我们不去渲染 li 标签了,
而是去渲染小组件 TodoItem 。
*/
<TodoItem />
)
})
}
</ul>
</Fragment>
)
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ""
})
}
handleItemDelete(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list: list
}
)
}
}
export default TodoList;
我们看看页面效果:
6️⃣-⑤:❌效果是有了,也没有报错(有一个让我们加唯一 key 值的警告,我们会在后边予以解决),但我们发现列表中展示的内容,并不是我们在 input 输入框中输入的内容!它是什么呢?
答:它是“小组件 TodoItem
”里边的内容—— <div>我是小组件 TodoItem 里的初始测试数据!</div>
。
即,每当我们往“数据项”list 里边增加一个内容,循环(map 方法)就会多显示一个 TodoItem 我是小组件 TodoItem 里的初始测试数据! 。
7️⃣❓怎样才能输入什么,列表项就给我们展示什么呢?
答:需要用到“组件间传值”的知识点。
7️⃣-①:如上图所示,实际项目开发过程中,整个需求页面可以被拆分成一个“组件树”的结构。
“父子”、“祖孙”、“相邻”……等等我们之前接触到的“结构”,在这里都适用。相应地,也涉及到了“组件”与“组件”间“传值”的问题。
如本例中,“ TodoList
父组件”如何传值给“ TodoItem
子组件”呢?——即,在“父组件 TodoList
”input 框输入什么“内容”,“子组件 TodoItem
”列表里就显示什么“内容”?
🏆React 中,父组件通过“属性”的形式向子组件传递数据。
打开 TodoList.js
文件:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>
提交
</button>
</div>
<ul>
{
this.state.list.map((item, index)=>{
return(
/*
7️⃣-②:在 map 方法中,回调函数的第一个“形参”即为列表(数组)的“每一项 item”,
我们又规定了回调函数返回的内容就是这个“每一项 item”。
所以,我们把“每一项 item”起名为 content,
并以“属性”的形式传给“子组件 TodoItem”。
*/
<TodoItem content={item}/>
)
})
}
</ul>
</Fragment>
)
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ""
})
}
handleItemDelete(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list: list
}
)
}
}
export default TodoList;
7️⃣-③:既然“父组件”把“每一项 item”起名为 content,并以“属性”的形式传给了“子组件 TodoItem”。那“子组件”应该怎么用呢?
或者说,“子组件”应该怎么接收“父组件”传递过来的这个值呢?
🏆React 中,子组件通过 this.props.属性
的形式来接收父组件传递过来的值。
打开 TodoItem.js
文件:
import React, { Component } from "react";
class TodoItem extends Component {
render() {
return(
<div>
{this.props.content} {/* 🚀子组件通过“this.props.属性”的形式来接收父组件传递过来的值。 */}
</div>
)
}
}
export default TodoItem;
看下页面效果(先不用管控制台的“警告”):
2 子组件向父组件传递数据
❓紧接上边的代码,我需要实现一个需求:当点击列表中的内容时,我需要将其从列表中删除。
答:
1️⃣既然是“点击”过后才出现的“删除”效果,那么首先就需要给列表项绑定一个“点击”事件。如今的“列表项”,已经被我们拆分成了一个“小组件”。要给“列表项”绑定事件,其实就是去给“小组件”绑定事件;
打开 TodoItem.js
文件:
import React, { Component } from "react";
class TodoItem extends Component {
render() {
return(
<div onClick={this.handleClick.bind(this)}> {/*
1️⃣-①:给子组件绑定一个“点击”事件(
注意借用 bind 改变 this 的指向。
另外,实际项目中这行代码还需要优化,
以免影响性能,我们将在下一篇集中优化
所有代码);
*/}
{this.props.content}
</div>
)
}
handleClick() { // 1️⃣-②:将“点击”事件具体要执行的方法放在这里;
}
}
export default TodoItem;
2️⃣“点击”事件绑定后,我们需要考虑一些很重要的问题:
-
需要确定点击的是列表中的哪一项——可以通过 map 方法循环的时候,回调函数中的“形参——每一项的下标 index”来确定点击的哪项,然后通过“属性”的形式传给“子组件
TodoItem
”; -
既然知道了“点击”项的 index,接下来就要考虑怎样把这个“点击”项在列表中删掉——子组件中的某项在被点击时,实质上是将“父组件
TodoList
”里的“数据项”list 中的“某项”删除。在父组件里,我们已经定义过一个删除方法handleItemDelete
。那么,就可以通过“属性”的形式将其传给“子组件TodoItem
”。然后,子组件再在自己的“点击”事件方法中调用即可(调用时,需传入“1 中子组件从父组件接收到的 index”)。
打开 TodoList.js
文件,先通过“属性”的形式传值给“子组件 TodoItem”:
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
list: []
};
}
render() {
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>
提交
</button>
</div>
<ul>
{
this.state.list.map((item, index)=>{
return(
/*
2️⃣-①:通过“属性”的形式,将 index 和 handleItemDelete 方法
传值给“子组件 TodoItem”;
*/
<TodoItem
content={item}
index={index}
itemDelete={this.handleItemDelete.bind(this)}
/>
/*
2️⃣-②:注意我在传递 handleItemDelete 方法时,我用了 .bind(this)
的写法。
为什么这样写呢?
答:“子组件”想要去掉其自身的某项,其实还是在借助“父组件”的方法 handleItemDelete,
子组件自己是没有这个方法的!我这里将 this 的“指向”规定为“父组件 TodoList”,
后边子组件调用时,this 的指向将不会出错。
*/
)
})
}
</ul>
</Fragment>
)
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ""
})
}
handleItemDelete(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list: list
}
)
}
}
export default TodoList;
打开 TodoItem
文件,对子组件进行相关方法的调用:
import React, { Component } from "react";
class TodoItem extends Component {
render() {
return(
<div onClick={this.handleClick.bind(this)}>
{this.props.content}
</div>
)
}
handleClick() {
this.props.itemDelete(this.props.index) /*
2️⃣-③:当点击列表中的某项时,
子组件调用父组件的“删除”方法,
并传入“某项”的“下标 index”。
*/
}
}
export default TodoItem;
看看页面效果(效果实现了,没报任何“错误”,只有一个“警告”。这个“警告”我们下一篇文章再解除!):
祝好,qdywxs ♥ you!
转载自:https://juejin.cn/post/7269563694677016576