react学习 --- 第五天 react组件化开发(一)

目录:

1 React的组件化开发

2 React组件生命周期

3 React组件间的通信

4 React组件插槽用法

5 React非父子的通信

6 setState的使用详解

一、React的组件化开发

 这里可以以2种方式导出内容:import { XXX } from "" 和import XXX from "",原因看下图的导出方式,这里用到的{ }不是解构。

 this.props和this.state发生改变时,就会调用render,

注意:1、render可以return react元素,这个元素就是标签,这样子可以做到插槽效果,父组件传递给子组件。可以被传递。render(){ return (<div></div>)}

2、返回数组的时候会自动遍历数组里面的东西然后渲染出来,比如["asd","qwe","ert"]会在页面中直接展示asdqweert,如果是[<h1>123</h1>,<h2>222</h2>,<h3>333</h3>]就会直接把里面的标签渲染出来。

render(){ return [<h1>123</h1>,<h2>222</h2>,<h3>333</h3>]}

3、如果渲染布尔值或null和undefined都不会渲染东西出来。

render(){ return undefined}

因为函数式组件不能用this关键字,所以不能使用this.state={}、this.setState({})等的方法。

函数式组件没有构造器,所以不能进行状态维护(后面讲到hook的时候就可以做到),就是说,在这个函数里面定义的变量,在每次调用的时候就会初始化变量,不保存你修改变量后的值。

早期的函数组件就是拿来展示内容的,传递一个变量到函数里然后展示这个变量。复杂的东西都是去类组件。有了hook之后就不一样了。

二、React组件生命周期

 

生命周期主要讲的是类组件的

类实例:定义了一个类组件然后创建了许多个标签,创建实例

 生命周期:挂载期间会执行:1、constructor构造函数;2、render渲染函数;3、componentDidMount函数。

数据发生变化的时候:1、某些方法调用了setState等方法的时候,会引起render函数的调用重新渲染界面;2、然后就会执行componentDidUpdate函数。

组件被卸载的时候:(这个作用之一是为了关闭卸载组件的某些事件监听,不怎么做的话会内存泄漏):直接调用componentDidWillUnmount函数。

 

 

shouldComponentUpdate函数就是控制组件要不要重新渲染界面的,return false 的话就是不重新渲染。

三、React组件间的通信

下载插件方便开发:

进入插件快捷输入的表格网站:(点击snippets)

 

react 父组件传递给子组件数据:

这里面constructor有保存数据的作用,子组件想要使用父组件传递过来的参数就需要使用 this.props的方法并在constructor和super里面写props,this.props方法和this.state的使用方法差不多。

这里的constructor方法是可以不用写也能做到使用父组件传递过来的参数,因为默认就有做这个props。例如:(不写constructor)

 props类型校验的使用方法:1、引入对应的库,2、写需要校验的变量对应的限制

 图片中的Greeting是指文件名,name是父组件传递给子组件的变量。

限制父组件传递给子组件的限制可以是:1、数组,2、Boolean,3、函数,4、数字,5、对象6、字符串,7、标识符8、node结点类型(包括element,注释,文本),9、react元素,10、自定义组件,11、实例,12、选定好的几个枚举类型、13几种类型中的任意一个类型。其余类型可以去官网高级查找。

子组件默认值写法以及es13新特性的子组件默认值的写法:

//父组件中:
import React, { Component } from 'react'
import Header from './header'


export class App extends Component {
    constructor(){
        super()
        this.state ={
            count:10
        }
    }
//定义一个给子组件使用的函数
    addCount(count){
      this.setState({count:this.state.count+count})
    }
  render() {
    return (
      <div>{this.state.count}
        
        {/* 传给子组件的addCount可以任意取名字 */}
        <Header  addCount={(count)=>this.addCount(count)}  />
      </div>
    )
  }
}

export default App

//子组件中:
import React, { Component } from 'react'

export class header extends Component {

//不用写constructor也能使用props的内容
//子组件需要再写一个函数,在这个函数里面使用this.props来获得从父组件获得的函数
  add(count){
    this.props.addCount(count)
  }
  sub(count){
    this.props.addCount(count)
  }
  render() {
    return (
      <div>
        <button onClick={()=>this.add(1)}>+1</button>
        <button onClick={()=>this.add(5)}>+5</button>
        <button onClick={()=>this.add(10)}>+10</button>
        <button onClick={()=>this.sub(-1)}>-1</button>
        <button onClick={()=>this.sub(-5)}>-5</button>
        <button onClick={()=>this.sub(-10)}>-10</button>
      </div>
    )
  }
}

export default header

//父组件代码:

import React, { Component } from 'react'
import Header from './header'


export class App extends Component {
    constructor(){
        super()
        this.state ={
            tabList:["表单一","表单二","表单三"],
            tabIndex:0
        }
    }
// 父组件给子组件传递的自定义函数,用于子组件和父组件传递参数
    onChangeCurrentIndex(index){
      this.setState({tabIndex:index})
//这里需要异步才能获取最新的变量值,毕竟是通过setState改变参数
      setTimeout(() => {
        console.log('tabIndex'+this.state.tabIndex)
      }, 0);
    }
    
  render() {
    const {tabList,tabIndex} = this.state
    return (
      <div>
        <Header tab={tabList} tabIndex={(index)=>this.onChangeCurrentIndex(index)} />
        <h1>{tabList[tabIndex]}</h1>
      </div>
    )
  }
}

export default App

//子组件代码:
import React, { Component } from 'react'
import './style.css'

export class header extends Component {
  constructor(){
    super()
    this.state={
      currentIndex:0
    }
  }
  itemClick(index){
    this.setState({currentIndex:index})
//使用父组件传过来的自定义函数
    this.props.tabIndex(index)
    setTimeout(() => {
      console.log(this.state.currentIndex)
    }, 0);
  }
  render() {
    const {tab} = this.props
    const {currentIndex} = this.state
    return (
      <div>header
        <div className='tab'>
          {tab.map((item,index)=>{
          return <span
           className={`item ${currentIndex ===index ?'active':''}`}
           key={index}
           onClick={()=>this.itemClick(index)}
            ><span className='text'>{item}</span></span>
          })}
        </div>
      </div>
    )
  }
}

export default header

//css样式:
.tab{
    display: flex;
}
.tab .item{
    flex: 1;
    text-align: center;
    
}
.tab .item.active{
    color: red;
    
}
.tab .item.active .text{
    padding: 0 5px;
    border-bottom:2px solid red;
    
}

四、React组件插槽用法

利用children来实现插槽需要注意:当父组件传递子组件的react元素是多个的时候,children在子元素的props里面是以数组形式存在的;当父组件传递给子组件的react元素是单个的时候,children在子元素的props里面是对象形式存在的,这个时候如果你还在使用children[xx]的话就会报错了,因为对象不是数组。当然单个使用react元素的话是按照对象格式获取。还有一个弊端是索引必须记住放在哪里,否则使用会出错。

 

建议使用下面这个父组件向子组件传递react元素的形式来做到插槽效果:

组件的作用域插槽:(既父组件传递给子组件不同的标签,而标签里面的动态数据需要从子组件的变量获取。这时候父组件获取不到子组件变量就需要使用作用域插槽)

在react中作用域插槽需要通过父组件传递给子组件函数的形式来完成,具体如下:

父组件:(写一个itemType的函数传递给子组件,其中button是自己选择的标签,item是参数) 

父组件也可以编写复杂的判断来给予不同的标签: 

子组件:(调用函数就可以)

五、React非父子的通信(了解)

第一种解决非父子通信的方法:当父组件传递给子组件的变量是对象类型时(不加...的话由于{}不能装对象类型,然后就会报错),可以通过...的格式传递,如下图。在子组件中两种方法的调用方法一样。

 

第二种解决非父子通信的方法:(这种方法也不是很好用,后期用redux的时候方便很多)

使用context的四步骤:

1、创建一个js文件,里面写入( ThemeContext自定义的变量名)

2、import引入到父组件,然后编写特定标签,并在标签里面写好value值:

 3、import引入到子组件:(第三步只能设置一个,不可以多个)4、使用参数:

 以上是类组件的使用context的方法,下面要用的是函数组件使用context 的方法:

1、引入编译context的文件,2、使用.consumer的标签,3、以函数的形式调用变量

 当多个嵌套context标签的时候也需要使用.consumer:(所以,.consumer在函数式组件和类组件都能使用,功能却不一样)

 上图中的home自定义组件可以使用两个上下文的value变量

 

 

上图的context文件的默认值(value)是给不在usercontext.provider标签内的自定义组件使用的

重要:不同组件间的通信:(将事件和参数都传递到目标组件,任意组件都可以发送事件和参数,任意组件也能监听事件的发生从而触发回调函数并拿到参数)

1、事件总线:npm install hy-event-store

2、创建util文件夹,在文件夹里面编写js文件,内容写入:

3、在发送事件和参数的组件里面import引入eventBus,利用eventBus.emit(事件名称,后续都是参数值)

 4、在需要监听的组件里面先import引入eventBus并在componentDidMount生命周期内监听eventBus.on(事件名称,回调函数),在componentWillUnmount移出监听

 移出监听在componentWillUnmount:

 上图中的bannerPrevClick函数在被eventBus.on(事件名称,回调函数)回调的时候,this指向为undefined,而这个时候我们是需要通过this修改state的变量的。这里有三种方法传递this给回调函数:

1、利用bind绑定this

 2、利用箭头函数

 3、利用eventBus.on(事件名称,回调函数,绑定this)

 

六、setState的使用详解(性能优化)

为什么要有setState?因为vue源码是有数据劫持,react没有数据劫持

为什么用基本用法setState修改一个变量,其他变量不会被覆盖消失?因为setState里面有记录新的参数和旧的参数,然后进行合并,而不是直接覆盖。

setState用法:

1、传入对象修改state里的变量 

2、传入回调函数

 3、setState在修改完state的变量值或函数时,由于是异步的,所以state的值真正改变的值是不能通过this.state马上获取,这时可以传入第二个回调函数来在你修改完state的变量值或函数后进行调用第二个回调函数。

 

面试常问:为什么setState是异步更新的?2点原因(主要看上图总结)

1、对于相同的setState操作,在异步的情况下,只会执行一次,最后的counter只会加一次一,render函数只执行一次。看下面这张图

 如果想要执行三次相同的setState,那么想要写成函数形式:此时counter会加三次1,但是render函数还是只执行一次。setState里面使用函数参数state都是获取最新的state的值,所以才能做到累加效果。看下面这张图

 

设置为异步的原因1:在一个方法里面多次调用setState的时候由于会执行多次render渲染UI界面,导致性能上的影响,所以使用异步的方法,可以统一获取setState然后一次性执行,避免多次执行render。

 ​​​​​

设置为异步的原因2:如果setState同步更新本组件的state的值,那么state的值会马上更新,但是render函数的里面的子组件如果用到了state里面的值,由于state更新了,render函数还没更新,导致子组件显示的变量是修改前的,本组件却是最新的,数据不一致。成为异步之后,发生setState的时候立马先更新render,然后再更新state,让这两件事在接近的时刻完成更新。不是完全同步的,因为单线程,只是看起来是完全同步。

 

 flushSync方法里面是可以执行多个setState方法的,里面的更新会马上执行,后续的console.log就能马上从state里面获取最新的变量值。flushSync使用前要先import引入。

猜你喜欢

转载自blog.csdn.net/weixin_56663198/article/details/128635577
今日推荐