React基础使用学习

元素与组件

如果代码多了之后,不可能一直在render方法里写,所以就需要把里面的代码提出来,定义一个变量,像这样:

	import React from 'react'
	import ReactDOM from 'react-dom'
	// 这里感觉又不习惯了?这是在用JSX定义一下react元素
	const app = <h1>欢迎进入React的世界</h1>
	ReactDOM.render(
	  app,
	  document.getElementById('root')
	)

函数式组件

由于元素没有办法传递参数,所以我们就需要把之前定义的变量改为一个方法,让这个方法去return一个元素:

	import React from 'react'
	import ReactDOM from 'react-dom'
	
	// 特别注意这里的写法,如果要在JSX里写js表达式(只能是表达式,不能流程控制),就需要加 {},包括注释也是一样,并且可以多层嵌套
	const app = (props) => <h1>欢迎进入{props.name}的世界</h1>
	
	ReactDOM.render(
	  app({
	    name: 'react'
	  }),
	  document.getElementById('root')
	)

这里我们定义的方法实际上也是react定义组件的第一种方式-定义函数式组件,这也是无状态组件。但是这种写法不符合react的jsx的风格,更好的方式是使用以下方式进行改造

	import React from 'react'
	import ReactDOM from 'react-dom'
	const App = (props) => <h1>欢迎进入{props.name}的世界</h1>
	ReactDOM.render(
	  // React组件的调用方式
	  <App name="react" />,
	  document.getElementById('root')
	)

这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。

class组件

ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:

	import React from 'react'
	import ReactDOM from 'react-dom'
	
	class App extends React.Component {
	  render () {
	    return (
	      // 注意这里得用this.props.name, 必须用this.props
	      <h1>欢迎进入{this.props.name}的世界</h1>
	  	)
	  }
	}
	ReactDOM.render(
	  <App name="react" />,
	  document.getElementById('root')
	)

运行结果和之前完全一样,因为JS里没有真正的class,这个class只是一个语法糖, 但二者的运行机制底层运行机制不一样。

  • 函数式组件是直接调用, 在前面的代码里已经有看到

  • es6 class组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:

        import React from 'react'
        import ReactDOM from 'react-dom'
        
        class App extends React.Component {
          render () {
            return (
          		<h1>欢迎进入{this.props.name}的世界</h1>
          	)
          }
        }
        
        const app = new App({
          name: 'react'
        }).render()
        
        ReactDOM.render(
          app,
          document.getElementById('root')
        )
    

组件的组合、嵌套

将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖

组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系

// 从 react 的包当中引入了 React 和 React.js 的组件父类 Component
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
      <h1>欢迎进入React的世界</h1>
  	)
  }
}
class Content extends Component {
  render () {
    return (
      <p>React.js是一个构建UI的库</p>
  	)
  }
}
/** 由于每个React组件只能有一个根节点,所以要渲染多个组件的时候,需要在最外层包一个容器,如果使用div, 会生成多余的一层dom
class App extends Component {
  render () {
    return (
    	<div>
    		<Title />
        <Content />
      </div>
  	)
  }
}
**/
// 如果不想生成多余的一层dom可以使用React提供的Fragment组件在最外层进行包裹
class App extends Component {
  render () {
    return (
      <Fragment>
      	<Title />
        <Content />
      </Fragment>
  	)
  }
}
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

JSX 原理

要明白JSX的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?

看下面的DOM结构

<div class='app' id='appRoot'>
  <h1 class='title'>欢迎进入React的世界</h1>
  <p>
    React.js 是一个帮助你构建页面 UI 的库
  </p>
</div>

上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:

{
  tag: 'div',
  attrs: { className: 'app', id: 'appRoot'},
  children: [
    {
      tag: 'h1',
      attrs: { className: 'title' },
      children: ['欢迎进入React的世界']
    },
    {
      tag: 'p',
      attrs: null,
      children: ['React.js 是一个构建页面 UI 的库']
    }
  ]
}

但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。

于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。

下面代码:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      <div className='app' id='appRoot'>
        <h1 className='title'>欢迎进入React的世界</h1>
        <p>
          React.js 是一个构建页面 UI 的库
        </p>
      </div>
    )
  }
}

ReactDOM.render(
	<App />,
  document.getElementById('root')
)

编译之后将得到这样的代码:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      React.createElement(
        "div",
        {
          className: 'app',
          id: 'appRoot'
        },
        React.createElement(
          "h1",
          { className: 'title' },
          "欢迎进入React的世界"
        ),
        React.createElement(
          "p",
          null,
          "React.js 是一个构建页面 UI 的库"
        )
      )
    )
  }
}

ReactDOM.render(
	React.createElement(App),
  document.getElementById('root')
)

React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等, 语法为

React.createElement(
  type,
  [props],
  [...children]
)

所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:

JSX —使用react构造组件,bable进行编译—> JavaScript对象 — ReactDOM.render()—>DOM元素 —>插入页面

#组件中DOM样式

  • 行内样式

想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:

// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
 <p style={{color:'red', fontSize:'14px'}}>你好</p>

行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如render函数里、组件原型上、外链js文件中

  • 使用class

React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体

其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,class需要写成className(因为毕竟是在写类js代码,会收到js规则的现在,而class是关键字)

<p className="hello" style = {this.style}>Hello world</p>

  • css-in-js

styled-components是针对React写的一套css-in-js框架,简单来讲就是在js中写css。npm链接

组件的数据挂载方式

属性(props)

props是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props

属性是描述性质、特点的,组件自己不能随意更改。

之前的组件代码里面有props的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props:

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
  		<h1>欢迎进入{this.props.name}的世界</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.name}是一个构建UI的库</p>
  )
}

class App extends Component {
  render () {
    return (
  		<Fragment>
      	<Title name="React" />
        <Content name="React.js" />
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

设置组件的默认props

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  // 使用类创建的组件,直接在这里写static方法,创建defaultProps
  static defaultProps = {
    name: 'React'
  }
  render () {
    return (
  		<h1>欢迎进入{this.props.name}的世界</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.name}是一个构建UI的库</p>
  )
}

// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
  name: 'React.js'
}

class App extends Component {
  render () {
    return (
  		<Fragment>
        {/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
      	<Title />
        <Content />
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

props.children

我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children 。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
  		<h1>欢迎进入{this.props.children}的世界</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.children}</p>
  )
}

class App extends Component {
  render () {
    return (
  		<Fragment>
      	<Title>React</Title>
        <Content><i>React.js</i>是一个构建UI的库</Content>
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

状态(state)

状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)

  • 定义state

第一种方式

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  state = {
    name: 'React',
    isLiked: false
  }
  render () {
    return (
      <div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button>
          {
            this.state.isLiked ? '❤️取消' : '🖤收藏'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

另一种方式(推荐)

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }
  render () {
    return (
  		<div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button>
          {
            this.state.isLiked ? '❤️取消' : '🖤收藏'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

this.propsthis.state是纯js对象,在vue中,data属性是利用Object.defineProperty处理过的,更改​data的数据的时候会触发数据的gettersetter,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState

  • setState

isLiked 存放在实例的 state 对象当中,组件的 render 函数内,会根据组件的 state 的中的isLiked不同显示“取消”或“收藏”内容。下面给 button 加上了点击的事件监听。

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }
  handleBtnClick = () => {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
  render () {
    return (
      <div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button onClick={this.handleBtnClick}>
          {
            this.state.isLiked ? '❤️取消' : '🖤收藏'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

setState有两个参数

第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做updater

  • 参数是对象

    this.setState({
      isLiked: !this.state.isLiked
    })
    
    
  • 参数是方法

    this.setState((prevState, props) => {
      return {
        isLiked: !prevState.isLiked
      }
    })
    
    

    注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props

setState是异步的,所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数

this.setState((prevState, props) => {
  return {
    isLiked: !prevState.isLiked
  }
}, () => {
  console.log('回调里的',this.state.isLiked)
})
console.log('setState外部的',this.state.isLiked)

受控组件与非受控组件

React组件的数据渲染是否被调用者传递的props完全控制,控制则为受控组件,否则非受控组件。

渲染数据

  • 条件渲染

    {
      condition ? '❤️取消' : '🖤收藏'
    }
    
    
  • 列表渲染

// 数据
const people = [{
  id: 1,
  name: 'Leo',
  age: 35
}, {
  id: 2,
  name: 'XiaoMing',
  age: 16
}]
// 渲染列表
{
  people.map(person => {
    return (
      <dl key={person.id}>
        <dt>{person.name}</dt>
        <dd>age: {person.age}</dd>
      </dl>
    )
  })
}

React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是key,这个 key 必须是每个元素唯一的标识

  • dangerouslySetInnerHTML

对于富文本创建的内容,后台拿到的数据是这样的:

content = "<p>React.js是一个构建UI的库</p>"

处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用dangerouslySetHTML属性,它允许我们动态设置innerHTML

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      content : "<p>React.js是一个构建UI的库</p>"
    }
  }
  render () {
    return (
  		<div
        // 注意这里是两个下下划线 __html
        dangerouslySetInnerHTML={{__html: this.state.content}}
      />
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

事件处理

绑定事件

采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick, React里的事件是驼峰onClickReact的事件并不是原生事件,而是合成事件

Event 对象

和普通浏览器一样,事件handler会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagationevent.preventDefault 这种常用的方法

事件的参数传递

  • render里调用方法的地方外面包一层箭头函数
  • render里通过this.handleEvent.bind(this, 参数)这样的方式来传递
  • 通过event传递
  • 比较推荐的是做一个子组件, 在父组件中定义方法,通过props传递到子组件中,然后在子组件件通过this.props.method来调用
import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      xing: '',
      ming: ''
    }
  }
  handleInputChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    })
  }
  render () {
    const {
      xing,
      ming
    } = this.state
    return (
  		<div>
        <label>
          <span>:</span>
          <input
            type="text"
            name="xing"
            value={xing}
            onChange={this.handleInputChange}
          />
        </label>
        <label>
          <span>:</span>
          <input
            type="text"
            name="ming"
            value={ming}
            onChange={this.handleInputChange}
          />
        </label>
        <p>欢迎您: {xing}{ming}</p>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

组件的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8tBZyMtj-1600353087265)(/Users/felix/Desktop/workspace/React.js/markdown/images/lifecycle-1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdKnvdr6-1600353087268)(/Users/felix/Desktop/workspace/React.js/markdown/images/lifycycle-2.png)]

https://www.jianshu.com/p/514fe21b9914

React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)

  • 初始化

在组件初始化阶段会执行

  1. constructor
  2. static getDerivedStateFromProps()
  3. componentWillMount() / UNSAFE_componentWillMount()
  4. render()
  5. componentDidMount()
  • 更新阶段

propsstate的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:

  1. componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
  2. static getDerivedStateFromProps()
  3. shouldComponentUpdate()
  4. componentWillUpdate() / UNSAFE_componentWillUpdate()
  5. render()
  6. getSnapshotBeforeUpdate()
  7. componentDidUpdate()
  • 卸载阶段
  1. componentWillUnmount()
  • 错误处理
  1. componentDidCatch()

各生命周期详解

  • 1.constructor(props)

React组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其他内容前,调用super(props),用来将父组件传来的props绑定到这个类中,使用this.props将会得到。

官方建议不要在constructor引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()

constructor中应当做些初始化的动作,如:初始化state,将事件处理函数绑定到类实例上,但也不要使用setState()。如果没有必要初始化state或绑定方法,则不需要构造constructor,或者把这个组件换成纯函数写法。

constructor(props) {
  super(props);
  this.state = {
    isLiked: props.isLiked
  };
}

  • 2.getDerivedStateFromProps(nextProps, prevState)

getDerivedStateFromProps 是react16.3之后新增,在组件实例化后,和接受新的props后被调用。他必须返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。

如果是由于父组件的props更改,所带来的重新渲染,也会触发此方法。

调用setState()不会触发getDerivedStateFromProps()

之前这里都是使用constructor+componentWillRecieveProps完成相同的功能的

  • -3. componentWillMount() / UNSAFE_componentWillMount()

UNSAFE_componentWillMount()在组件挂载前被调用,在这个方法中调用setState()不会起作用,是由于他在render()前被调用。

为了避免副作用和其他的订阅,官方都建议使用componentDidMount()代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。

  • 4.render()

render()方法是必需的。当他被调用时,他将计算this.propsthis.state,并返回以下一种类型:

  1. React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
  2. 字符串或数字。他们将会以文本节点形式渲染到dom中。
  3. Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
  4. null,什么也不渲染
  5. 布尔值。也是什么都不渲染。
createPortal用法
render() {
  // React does *not* create a new div. It renders the children into `domNode`.
  // `domNode` is any valid DOM node, regardless of its location in the DOM.
  return ReactDOM.createPortal(
    app,//React元素
    domNode,//要插入的dom节点
  );
}

当返回null,false,ReactDOM.findDOMNode(this)将会返回null,什么都不会渲染。

render()方法必须是一个纯函数,他不应该改变state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()返回falserender()不会被调用。

  • -5. componentDidMount

componentDidMount在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。

通常在这里进行ajax请求

如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.

  • 6.componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)

官方建议使用getDerivedStateFromProps函数代替componentWillReceiveProps。当组件挂载后,接收到新的props后会被调用。如果需要更新state来响应props的更改,则可以进行this.propsnextProps的比较,并在此方法中使用this.setState()

如果父组件会让这个组件重新渲染,即使props没有改变,也会调用这个方法。

React不会在组件初始化props时调用这个方法。调用this.setState也不会触发。

  • 7.shouldComponentUpdate(nextProps, nextState)

调用shouldComponentUpdate使React知道,组件的输出是否受stateprops的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。

在渲染新的propsstate前,shouldComponentUpdate会被调用。默认为true。这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。返回false不会阻止子组件在state更改时重新渲染。

如果shouldComponentUpdate()返回falsecomponentWillUpdate,rendercomponentDidUpdate不会被调用。

官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。

  • 8.UNSAFE_componentWillUpdate(nextProps, nextState)

在渲染新的stateprops时,UNSAFE_componentWillUpdate会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。

不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新stateprops,调用getDerivedStateFromProps

  • 9.getSnapshotBeforeUpdate()

在react render()后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。

  • 10.componentDidUpdate(prevProps, prevState, snapshot)

在更新发生后立即调用componentDidUpdate()。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。

如果组件实现getSnapshotBeforeUpdate()生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()。否则,这个参数是undefined

  • 11.componentWillUnmount()

在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount中创建的任何监听。

  • 12.componentDidCatch(error, info)

错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。

PureComponent

PureComponnet里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate,而且。是否重新渲染以shouldComponentUpdate的返回值为最终的决定因素。

import React, { PureComponent } from 'react'

class YourComponent extends PureComponent {
  ……
}

ref

React提供的这个ref属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()返回的组件实例,ref可以挂载到组件上也可以是dom元素上。

  • 挂到组件(class声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:
  • 挂载到dom元素上时表示具体的dom元素节点。

在React 新的版本中,要使用ref, 需要使用React.createRef方法先生成一个ref

import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    // 创建inputRef
    this.inputRef=createRef()
  }
  componentDidMount () {
    console.log(this.inputRef.current) // <input type="text">
  }
  render () {
    return (
  		<div>
        {/* 关联ref和dom */}
        <input type="text" ref={this.inputRef} />
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

组件通信

父组件与子组件通信

  • 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
  • 父组件利用ref对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法…

子组件与父组件通信

  • 父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过this.props接收到父组件的方法后调用。

跨组件通信

在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了context api来实现跨组件通信, React 16.3之后的contextapi较之前的好用。

// counterContext.js
import React, { Component, createContext } from 'react'

const {
  Provider,
  Consumer: CountConsumer
} = createContext()

class CountProvider extends Component {
  constructor () {
    super()
    this.state = {
      count: 1
    }
  }
  increaseCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  decreaseCount = () => {
    this.setState({
      count: this.state.count - 1
    })
  }
  render() {
    return (
      <Provider value={{
        count: this.state.count,
        increaseCount: this.increaseCount,
        decreaseCount: this.decreaseCount
      }}
      >
        {this.props.children}
      </Provider>
    )
  }
}

export {
  CountProvider,
  CountConsumer
}

// 定义CountButton组件
const CountButton = (props) => {
  return (
    <CountConsumer>
      // consumer的children必须是一个方法
      {
        ({ increaseCount, decreaseCount }) => {
          const { type } = props
          const handleClick = type === 'increase' ? increaseCount : decreaseCount
          const btnText = type === 'increase' ? '+' : '-'
          return <button onClick={handleClick}>{btnText}</button>
        }
      }
    </CountConsumer>
  )
}

// 定义count组件,用于显示数量
const Count = (prop) => {
  return (
    <CountConsumer>
      {
        ({ count }) => {
          return <span>{count}</span>
        }
      }
    </CountConsumer>
  )
}

// 组合
class App extends Component {
  render () {
    return (
  		<CountProvider>
	        <CountButton type='decrease' />
	        <Count />
	        <CountButton type='increase' />
      	</CountProvider>
  	)
  }
}

复杂的非父子组件通信在react中很难处理,多组件间的数据共享也不好处理,在实际的工作中我们会使用flux、redux、mobx来实现

HOC(高阶组件)

Higher-Order Components就是一个函数,传给它一个组件,它返回一个新的组件。

const NewComponent = higherOrderComponent(YourComponent)

比如,我们想要我们的组件通过自动注入一个版权信息。

// withCopyright.js 定义一个高阶组件
import React, { Component, Fragment } from 'react'

const withCopyright = (WrappedComponent) => {
  return class NewComponent extends Component {
    render() {
      return (
        <Fragment>
          <WrappedComponent />
          <div>&copy;wwming </div>
        </Fragment>
      )
    }
  }
}
export default withCopyright

// 使用方式
import withCopyright from './withCopyright'

class App extends Component {
  render () {
    return (
  		<div>
        <h1>Awesome React</h1>
        <p>React.js是一个构建用户界面的库</p>
      </div>
  	)
  }
}
const CopyrightApp = withCopyright(App)

这样只要我们有需要用到版权信息的组件,都可以直接使用withCopyright这个高阶组件包裹即可。

React Hooks

React Hooks 是 React 16.7.0-alpha 版本推出的新特性, 有了React Hooks,在 react 函数组件中,也可以使用类组件(classes components)的 state 和 组件生命周期。通过下面几个例子来学习React Hooks。

  • State Hook
// useState是react包提供的一个方法
import React, { useState } from "react";
import ReactDOM from "react-dom";

const Counter = () => {
  // useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>你点击了{count}</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

const rootElement = document.getElementById("root");

ReactDOM.render(<Counter />, rootElement);

  • Effect Hook
// useState是react包提供的一个方法
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Counter = () => {
  // useState 这个方法可以为我们的函数组件拥有自己的state,它接收一个用于初始 state 的值,返回一对变量。这里我们把计数器的初始值设置为0, 方法都是以set开始
  const [count, setCount] = useState(0);
  // 类似于componentDidMount或者componentDidUpdate:
  useEffect(() => {
    // 更改网页的标题,还可以做其它的监听
    document.title = `你点击了${count}次`;
  });
  return (
    <div>
      <p>你点击了{count}</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
};

const rootElement = document.getElementById("root");

ReactDOM.render(<Counter />, rootElement);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.温m_

如果喜欢可以支持打赏小哥!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值