React-组件进阶


组件进阶

组件通讯

多个组件之间需要共享某些数据,为了实现这个功能,打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯  

组件的props

作用:接收传递给组件的数据 传递数据:给组件标签添加属性 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
//props使用
//函数组件接收数据
const Hello = (props) => {
  //props是一个对象
  console.log(props)
  return (
    

props:{props.name}

) } ReactDOM.render(, document.getElementByI
//类组件接收数据
class Hello extends React.Component {
  render() {
    console.log(this.props);
    return (
      

props:{this.props.name}

) } } ReactDOM.render(, document.getElementById('root'));
特点 可以传递任意类型的数据 props是只读对象,只能读取属性的值,无法修改对象 使用类组件是,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到props
class Hello extends React.Component {
  render() {
    console.log(this.props);
    this.props.fn()

    return (
      

props:{this.props.name}

{this.props.tag}
) } } ReactDOM.render(} colors={['red', 'blue']} fn={() => { console.log(123); }} tag={

123

} />, document.getElementById('root'));

组件通讯的三种方式

1.父组件传递数据给子组件
//父组件
class Parent extends React.Component {
  state = {
    lastName: '王'
  }
  render() {
    return (
      
父组件: this.state.lastName} />
) } } //子组件 const Child = (props) => { return (

子组件,接收到父组件的数据{props.name}

) } ReactDOM.render(, document.getElementById('root'))

2.子组件传递数据给父组件

//父组件
class Parent extends React.Component {
  state = {
    parentMsg: ''
  }
  //回掉函数 用来接收数据
  getChildMsg = data => {
    console.log('接收到子组件传输的数据', data);
    this.setState({
      parentMsg: data
    })
  }
  render() {
    return (
      
父组件:{this.state.parentMsg} this.getChildMsg} />
) } } //子组件 class Child extends React.Component { state = { Msg: 'dddddddddddd' } handleClick = () => { //子组件调用父组件的回掉函数 this.props.getMsg(this.state.Msg) } render() { return (
子组件:
) } } ReactDOM.render(, document.getElementById('root'))

3.兄弟组件

//父组件
class Counter extends React.Component {
  //共享状态
  state = {
    count: 0
  }
  //提供修改状态的方法
  onIncrement = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return (
      
this.state.count} /> this.onIncrement} />
) } } const Child1 = (props) => { return (

计数器:{props.count}

) } const Child2 = (props) => { return } ReactDOM.render(, document.getElementById('root'))

Context

作用:跨组件传递数据(比如:主题、语言) 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。
//创建context组件
const { Provider, Consumer } = React.createContext()
class App extends React.Component {
  render() {
    return (
      
        
) } } const Node = (props) => { return (
) } const SubNode = (props) => { return (
) } const Child = (props) => { return (
{ (data) => 我是子节点---{data} }
) } ReactDOM.render(, document.getElementById('root'))

props深入

1.children属性 组件标签的子节点,当组件标签有子节点时,props就会有该属性 children属性与普通的props一样,值可以为任意值(文本、React元素,组件,函数等)
const App = props => {
  console.log(props);
  return (
    

组件标签的子节点:

{props.children}
) } ReactDOM.render(我是子节点, document.getElementById('root'))
const Test = () => 
const App = props => {
  console.log(props);
  return (
    

组件标签的子节点:

{props.children}
) } ReactDOM.render( {/*

我是p标签

*/}
, document.getElementById('root'))
const Test = () => {
  return ()
}
const App = props => {
  console.log(props);
  props.children()
  return (
    

组件标签的子节点:

{/* {props.children} */}
) } ReactDOM.render( {() => console.log('这是一个函数子节点')} , document.getElementById('root'))
2.props校验 安装prop-types包:npm i prop-types
const App = props => {
  const arr = props.colors
  const lis = arr.map((item, index) => 
  • {item}
  • ) return
      {lis}
    } //添加props校验 App.propTypes = { colors: PropTypes.array } ReactDOM.render(, document.getElementById('root'))
    //添加props校验
    //属性a的类型:数值(number)
    //属性fn的类型:函数(func)并且为必填项
    //属性tag的类型:React元素(element)
    //属性filter的类型:对象({area:'上海',price:1999})
    App.propTypes = {
      colors: PropTypes.array,
      a: PropTypes.number,
      fn: PropTypes.func.isRequired,
      tag: PropTypes.element,
      filter: PropTypes.shape({
        area: PropTypes.string,
        price: PropTypes.number
      })
    }
    ReactDOM.render( { }} />, document.getElementById('root'))
    props的默认值 场景:分页组件-》每页显示的条数 
    const App = props => {
      console.log(props)
      return (
        

    此处展示props的默认值:{props.pagesize}

    ) } //添加props默认值 App.defaultProps = { //当不传入pagesize是,则显示默认的10 pagesize: 10 } ReactDOM.render(, document.getElementById('root'))

    组件的生命周期

    组件从被创建到挂载到页面中运行,再到组件不用时写在的过程  生命周期的每个阶段总伴随着一些方法调用,这些方法就是生命周期的钩子函数 钩子函数的作用,为开发人员在不同阶段操作组件提供了时机。 只有类组件才有生命周期  

    生命周期的三个阶段

    1.创建时(挂载阶段)
    class App extends React.Component {
    
      constructor(props) {
        super(props)
        //初始化state
        this.state = {
          count: 0
        }
        //处理this指向问题
    
        console.warn('生命周期钩子函数:constructor');
        //获取不到,因为没有渲染结束,结果为null
        // const title = document.getElementById('title');
        // console.log(title);
      }
      //进行dom操作
      //能够发送ajax请求,获取远程的数据
      componentDidMount() {
        console.warn('生命周期钩子函数:componentDidMount');
        //渲染结束之后,可以获取title
        // const title = document.getElementById('title');
        // console.log(title);
      }
      render() {
        // //错误提示 不要在render中调用setState
        // this.setState({
        //   count: 1
        // })
        console.warn('生命周期钩子函数:render');
        return (
          

    统计打豆豆的次数

    ) } } ReactDOM.render(, document.getElementById('root'))
    2.更新时 执行时机:1.setState() 2forceUpdate 3.组件接收到新的props
    class App extends React.Component {
      constructor(props) {
        super(props)
        //初始化state
        this.state = {
          count: 0
        }
      }
      handleClick = () => {
        // this.setState({
        //   count: this.state.count + 1
        // })
        this.forceUpdate()
      }
      componentDidUpdate() {
        console.warn('---子组件生命周期钩子函数 componentDidUpdate');
        //获取dom
        const title = document.getElementById('title')
        console.log(title);
      }
      render() {
        console.warn('生命周期钩子函数 render');
        return (
          
    this.state.count} />
    ) } } class Counter extends React.Component { render() { console.warn('---子组件生命周期钩子函数 render'); return

    统计豆豆被打的次数{this.props.count}

    } } ReactDOM.render(, document.getElementById('root'))

     3.卸载时

    class App extends React.Component {
      constructor(props) {
        super(props)
        //初始化state
        this.state = {
          count: 0
        }
      }
      handleClick = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
      render() {
        return (
    
          
    {this.state.count > 3 ? (

    豆豆被打死了

    ) : () }
    ) } } class Counter extends React.Component { componentDidMount() { //开启定时器 this.timeid = setInterval(() => { console.log('定时器正在执行'); }, 500) } render() { return

    统计豆豆被打的次数{this.props.count}

    } componentWillUnmount() { console.warn('生命周期钩子函数 componentWillUnmount'); //清理定时器 clearInterval(this.timeid) } } ReactDOM.render(, document.getElementById('root'))

    render-props和高阶组件

    React组件复用概述 思考:如果两个组件中的部分功能相似或相同,该如何处理 处理方式:复用相似的功能 复用什么:1.state 2.操作state的方法  两种方式:1.render props模式 2.高阶组件(HOC) 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化成的固定模式。   Render props模式 问题1:如何难道该组件中复用的state? 在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现) 问题2:如何渲染任意的UI 使用该函数的返回值作为要渲染的UI内容(需要组件你饿不实现)
    /render props模式
    //mouse组件创建
    class Mouse extends React.Component {
    
      propsTypes = {
    
      }
      state = {
        x: 0,
        y: 0
      }
      handleMouseMove = e => {
        this.setState({
          x: e.clientX,
          y: e.clientY
        })
      }
      //监听鼠标移动事件
      componentDidMount() {
        window.addEventListener('mousemove', this.handleMouseMove)
      }
      //在组件卸载时移除事件绑定
      componentWillUnmount() {
        window.addEventListener('mousemove', this.handleMouseMove)
      }
    
      render() {
        return this.props.children(this.state)
      }
    }
    //添加props校验
    Mouse.propTypes = {
      children: PropTypes.func.isRequired
    }
    class App extends React.Component {
      render() {
        return (
          

    render props 模式

    {/* { return

    鼠标当前位置{mouser.x},{mouser.y}

    }} />
    */} { mouser => { return (

    鼠标当前位置{mouser.x},{mouser.y}

    ) } }
    {/* { return girl }}> */} { mouser => ( girl{{ position: 'absolute', top: mouser.y, left: mouser.x } } /> )}
    ) } } ReactDOM.render(, document.getElementById('root'))

    高阶组件

    目的:实现状态逻辑复用 采用包装模式,比如说手机壳 思路分析:
    • 高阶组件(HOC)是一个函数,接收要包装的组件,返回增强后的组件
    • 高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑,通过prop将复用的状态传递给被包装组件
    //高阶组件
    //创建高阶组件
    function withMouse(WrappedComponent) {
      //该组件提供复用的状态逻辑
      class Mouse extends React.Component {
        state = {
          x: 0,
          y: 0
        }
        hanldemouseMove = e => {
          this.setState({
            x: e.clientX,
            y: e.clientY
          })
        }
        componentDidMount() {
          window.addEventListener('mousemove', this.hanldemouseMove)
        }
        componentWillUnmount() {
          window.removeEventListener('mousemove', this.hanldemouseMove)
        }
        render() {
          return this.state} />
        }
      }
      return Mouse
    }
    const Position = props => (
      

    鼠标当前位置:(x:{props.x},y:{props.y})

    ) //加入图片 const Girl = props => { return ( girl{{ position: 'absolute', top: props.y, left: props.x } } />) } //获取增强后的组件 const Mouseposition = withMouse(Position) //调用图片迁移的高阶组件 const MouseGirl = withMouse(Girl) class App extends React.Component { render() { return (

    高阶组件

    ) } } ReactDOM.render(, document.getElementById('root'))
    设置displayname 使用高阶组件存在的问题:得到两个组件名称相同 原因:默认情况下,React使用组件名称作为displayName 解决方式:为高阶组件设置displayName便于调试时区分不同的组件 display Name的作用:用于设置调试信息
    //设置display
      Mouse.displayName = 'WithMouse' + getDisplayName(WrappedComponent)
    
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component'
    
    }

    传递props

    问题:props丢失 原因:高阶组件没有往下传递props 解决方式:渲染时,将state和this.props一起传递给租价 传递方式