likes
comments
collection
share

(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

作者站长头像
站长
· 阅读数 12
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

1 父组件向子组件传递数据

紧接上一篇的代码,我们实现了 TodoList 的相关功能。针对这个超级简单的页面,我们依然能够用“组件”化的思维进行改写。

❓拆分组件时,我们应该是一个怎样的思考和实践过程呢? 答:

1️⃣查看项目的 index.js 文件,这个文件是项目的“入口文件”。在这个文件里,我们清楚地看到: (08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

即,我们在入口文件里,仅把 TodoList 这个大“组件”挂载到 id 为 root 的元素上。

2️⃣既然仅仅挂载一个 TodoList 组件,即表示:最终页面展示的所有内容,都在 TodoList 这一个组件之中。

3️⃣最外层的“大组件”我们知道是谁了,接下来就可以对这个“大组件”进行拆分: (08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

4️⃣“组件”拆分好后,我们在 src 目录下新增一个文件 TodoItem.js(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

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;

  我们看看页面效果:

(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

6️⃣-⑤:❌效果是有了,也没有报错(有一个让我们加唯一 key 值的警告,我们会在后边予以解决),但我们发现列表中展示的内容,并不是我们在 input 输入框中输入的内容!它是什么呢?

答:它是“小组件 TodoItem ”里边的内容—— <div>我是小组件 TodoItem 里的初始测试数据!</div> 。 即,每当我们往“数据项”list 里边增加一个内容,循环(map 方法)就会多显示一个 TodoItem 我是小组件 TodoItem 里的初始测试数据! 。

7️⃣❓怎样才能输入什么,列表项就给我们展示什么呢? 答:需要用到“组件间传值”的知识点。 (08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

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;

看下页面效果(先不用管控制台的“警告”):

(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

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️⃣“点击”事件绑定后,我们需要考虑一些很重要的问题:

  1. 需要确定点击的是列表中的哪一项——可以通过 map 方法循环的时候,回调函数中的“形参——每一项的下标 index”来确定点击的哪项,然后通过“属性”的形式传给“子组件 TodoItem ”;

  2. 既然知道了“点击”项的 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;

看看页面效果(效果实现了,没报任何“错误”,只有一个“警告”。这个“警告”我们下一篇文章再解除!):

(08)React 入门——⑤ 拆分组件和组件间传值 | React 基础理论实操

祝好,qdywxs ♥ you!