重拾React

组件

组件介绍

  1. 组件是React的一等公民,使用React就是在使用组件
  2. 特点:可复用、独立、可组合

组件创建方式

函数创建组件

  • 函数必须要大写字母开头
  • 函数组件必须要有返回值,标识该组件的结构
  • 使用函数名作为组件标签名
function Hello () {
  return (
    <div>这是Hello组件</div>
  )
}
ReactDOM.render(<Hello/>,document.getElementById('root'))
// 箭头函数  
const Hello = () => <div>这是箭头函数Hello组件</div>
ReactDOM.render(<Hello/>,document.getElementById('root'))

类创建组件

  • 使用ES6的class创建组件
  • 类名称大写字母开头
  • 类组件继承React.Component父类,从而可以使用父类中提供的方法或属性
  • 类组件必须提供render方法

抽离为独立js文件

  • 创建js文件
  • 导入React
  • 创建组件
  • 在js文件内导出该组件
  • 在index.js文件中引入组件
import React, { Component } from 'react'

export default class Hello extends Component {
  render() {
    return (
      <div>
        <div>抽离类组件Hello</div>
      </div>
    )
  }
}

import Hello from './view/Hello/Hello';
ReactDOM.render(<Hello/>,document.getElementById('root'))

事件处理

事件绑定

  • 语法:on+事件名称={事件处理程序},eg:onClick={()=>{}}
  • 事件采用驼峰命名法
import React, { Component } from 'react'

export default class Click extends Component {
  render() {
    return (
      <div>
        <button onClick={hindClick}>按钮</button>
      </div>
    )
    function hindClick () {
      console.log("按钮被点击");
    }
  }
}

事件对象

  • 通过事件处理程序的参数获取到事件对象
  • React的事件对象叫做:合成事件
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
import React, { Component } from 'react'

export default class Click extends Component {
  render() {
    return (
      <div>
        <a href="http://www.baidu.com" onClick={hindClick}>百度</a>
      </div>
    )
    function hindClick (e) {
      // 阻止浏览器默认行为  
      e.preventDefault();
      console.log("标签被点击");
    }
  }
}

有状态组件和无状态组件

  • 函数组件:无状态组件,无自己的状态,只负责静态数据展示
  • 类组件:有状态组件,有自己的状态,负责UI更新,让页面动起来

组件中的state和setState()

state基本使用

  • state即数据,是组件内部私有的数据,只在组件内部使用
  • state的值是一个对象,表示一个组件中可以有多个数据
  • 通过this.state获取

setState状态修改

  • 状态可改变
  • 语法:this.setState({需要修改的数据})
  • 注意:不要直接修改state中的值,这种操作是错的
  • setState的作用:1.修改state 2. 更新UI
  • 思想:数据驱动视图
import React, { Component } from 'react'

export default class Test extends Component {

  state = {
    count: 0
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={()=>{
          this.setState({
            count:this.state.count+1
          })
        }}>+1</button>
      </div>
    )
  }
}

jsx中抽离事件处理程序

import React, { Component } from 'react'

export default class Test extends Component {

  state = {
    count: 0
  }
  addCount() {
    this.setState({
      count:this.state.count+1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.addCount}>+1</button>
      </div>
    )
  }
}

  • 会报错: TypeError: Cannot read property 'setState' of undefined
  • 原因;this指向undefined
  • 期望:this指向组件实例(render方法中的this就是组件实例)

事件绑定this指向

箭头函数

  • 箭头函数中得到this指向外部环境
import React, { Component } from 'react'
export default class Test extends Component {
  state = {
    count: 0
  }
  addCount() {
    this.setState({
      count:this.state.count+1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={()=>this.addCount()}>+1</button>
      </div>
    )
  }
}

Function.prototype.bind()

  • 利用ES5中的bind()方法,将事件处理程序中的this与组件实例绑定到一起
import React, { Component } from 'react'
export default class Test extends Component {
  constructor() {
    super()
    this.state = {
      count: 0
    }
    this.addCount = this.addCount.bind(this)
  }
  addCount() {
    this.setState({
      count:this.state.count+1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.addCount}>+1</button>
      </div>
    )
  }
}

class实例方法

  • 利用箭头函数形式的class实例方法
  • 注意:是实验性语法,但是由于bable的存在可以直接使用
import React, { Component } from 'react'
export default class Test extends Component {
  constructor() {
    super()
    this.state = {
      count: 0
    }
    this.addCount = this.addCount.bind(this)
  }
   
  addCount = () =>{
    this.setState({
      count:this.state.count+1
    })
  }

  render() {
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.addCount}>+1</button>
      </div>
    )
  }
}

表单处理

受控组件

  • HTML中的表达元素是可输入的,也就是有自己的可变状态
  • 而React中可变状态通常保存在state中,并且只能通过setState()方法来修改
  • React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
  • 步骤:
  1. state中添加一个状态,作为表单元素的value值(控制表单元素值得来源)
  2. 给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素的变化)
import React, { Component } from 'react'
export default class Test extends Component {
  constructor() {
    super()
    this.state = {
      txt:'',
      content:'',
      city:'bj',
      isChecked:false
    }
    
  }
  handleChange = e =>{
    this.setState({
      txt:e.target.value
    })
  }
  handleContent = e=>{
    this.setState({
      content:e.target.value
    })
  }
  handleCity = e=>{
    this.setState({
      city:e.target.value
    })
  }
  handleCheck = e=>{
    this.setState({
      isChecked:e.target.checked
    })
  }
  render() {
    const {content,txt,city,isChecked} = this.state
    return (
      <div>
        {/* 文本框 */}
        <input type="text" value={txt} onChange={this.handleChange}/><br />
        {/* 富文本框 */}
        <textarea value={content} onChange={this.handleContent}></textarea><br />
        {/* 下拉框 */}
        <select value={city} onChange={this.handleCity}>
          <option value="bj">北京</option>
          <option value="sh">上海</option>
          <option value="xa">西安</option>
        </select><br />
        {/* 复选框 */}
        <input type="checkbox" checked={isChecked} onChange={this.handleCheck}/>
      </div>
    )
  }
}

多表单元素优化

  • 给表单元素添加name属性,名称与state相同
  • 根据表单元素类型获取对应的值
  • change水煎处理程序中通过[name]来修改对应的state
import React, { Component } from 'react'
export default class Test extends Component {
  constructor() {
    super()
    this.state = {
      txt:'',
      content:'',
      city:'bj',
      isChecked:false
    }
    
  }
  handleChange = e =>{
    const target = e.target;
    const value = target.type==='checkbox'
      ? target.checked
      : target.value
    const name = target.name
    this.setState({
      [name]:value
    })
  }
  
  render() {
    const {txt,content,city,isChecked} = this.state
    return (
      <div>
        {/* 文本框 */}
        <input type="text" name='txt' value={txt} onChange={this.handleChange}/><br />
        {/* 富文本框 */}
        <textarea name='content' value={content} onChange={this.handleChange}></textarea><br />
        {/* 下拉框 */}
        <select name='city' value={city} onChange={this.handleChange}>
          <option value="bj">北京</option>
          <option value="sh">上海</option>
          <option value="xa">西安</option>
        </select><br />
        {/* 复选框 */}
        <input type="checkbox" name='isChecked' checked={isChecked} onChange={this.handleChange}/>
      </div>
    )
  }
}

非受控组件

  • 调用React.createRef()方法创建一个ref对象
constructor() {
    super();
    // 创建ref对象  
    this.txtRef = React.createRef()
}
  • 将创建好的ref对象添加到文本框中
<input type="text" ref={this.txtRef}/>
  • 通过ref对象获取文本框的值
console.log(this.txtRef.current.value);
import React, { Component } from "react";
export default class Test extends Component {
  constructor() {
    super();
    // 创建ref对象  
    this.txtRef = React.createRef()
  }
  getText = ()=>{
    console.log(this.txtRef.current.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref={this.txtRef}/>
        <button onClick={this.getText}>获取文本框的值</button>
      </div>
    );
  }
}

DEMO练手

  1. 渲染评论列表
import React, { Component } from 'react'

export default class List extends Component {
  state = {
    comments: [
      { id: 12, name: 'jack', content: '666' },
      { id: 13, name: 'WK', content: '哈哈' },
      { id: 122, name: 'Mgd', content: '楼上是傻子' },

    ]
  }
  renderList = (comments) => {
    return comments.length === 0
      ? (<div>暂无评论,快去评论吧</div>)
      : (
        <ul>
          {
            comments.map(item =>
            (
              <li key={item.id}>
                <h3>评论人:{item.name}</h3>
                <p>评论内容:{item.content}</p>
              </li>
            )
            )
          }
        </ul>
      )
  }

  render() {
    const { comments } = this.state;
    return (
      <div>
        {/* 条件渲染 */}
        {
          this.renderList(comments)
        }
      </div>
    )
  }
}
  1. 获取表单信息
import React, { Component } from 'react'

export default class List extends Component {
  state = {
    userName:'',
    userContent:''
  }
  
  handleFrom = e=>{
    const {name, value} = e.target;
    this.setState({
      [name]:value
    })
  }

  render() {
    const { userName, userContent} = this.state;
    return (
      <div>
        <div>
          <label htmlFor="">评论人</label>
          <input type="text" name='userName' value={userName} onChange={this.handleFrom}/><br />
          <label htmlFor="">评论内容</label>
          <textarea name="userContent" value={userContent} onChange={this.handleFrom}></textarea><br />
          <button>提交评论</button>
        </div>
      </div>
    )
  }
}
  1. 发表评论
import React, { Component } from 'react'

export default class List extends Component {
  state = {
    comments: [
      { id: 12, name: 'jack', content: '666' },
      { id: 13, name: 'WK', content: '哈哈' },
      { id: 122, name: 'Mgd', content: '楼上是傻子' },
    ],
    userName:'',
    userContent:''
  }
  renderList = (comments) => {
    return comments.length === 0
      ? (<div>暂无评论,快去评论吧</div>)
      : (
        <ul>
          {
            comments.map(item =>
              (
                <li key={item.id}>
                  <h3>评论人:{item.name}</h3>
                  <p>评论内容:{item.content}</p>
                </li>
              )
            )
          }
        </ul>
      )
  }
  handleFrom = e=>{
    const {name, value} = e.target;
    this.setState({
      [name]:value
    })
  }
  addContent = ()=>{
    const { comments, userName, userContent} = this.state;
    if(userName.trim()===''||userContent.trim()===''){
      alert('请输入正确的评论');
      return;
    }
    const newComments = [{
      id:Math.random(),
      name:userName,
      content:userContent
    },...comments];
    this.setState({
      comments:newComments,
      userName:'',
      userContent:'',
    })
  }

  render() {
    const { comments, userName, userContent} = this.state;
    return (
      <div>
        <div>
          <label htmlFor="">评论人</label>
          <input type="text" name='userName' value={userName} onChange={this.handleFrom}/><br />
          <label htmlFor="">评论内容</label>
          <textarea name="userContent" value={userContent} onChange={this.handleFrom}></textarea><br />
          <button onClick={this.addContent}>提交评论</button>
        </div>
        {/* 条件渲染 */}
        {
          this.renderList(comments)
        }
      </div>
    )
  }
}

组件高阶使用

组件的props

  • 组件是封闭的,要接受数据应该通过props来实现
  • props的作用:接受传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接受数据:函数组件通过参数props接受数据,类组件通过this.props接受数据

函数组件:

function Hello (props) {
  return (
    <div>props:{props.name}</div>
  )
}

ReactDOM.render(<Hello name='Mgd' age={22} />,document.getElementById('root'))

类组件

import React, { Component } from 'react'

export default class Hello extends Component {
  render() {
    return (
      <div>
        {/* <div>抽离类组件Hello</div> */}
        <div>props:{this.props.age}</div>
      </div>
    )
  }
}


ReactDOM.render(<Hello name='Mgd' age={22} />,document.getElementById('root'))
  1. 特点
  • 可以给组件传递任意类型的数据
function Hello (props) {
  props.fn();
  return (
    <div>
      props:{props.name}
      {props.tag}
    </div>
    
  )
}
ReactDOM.render(<Hello name='Mgd' age={22} color={['blue','red','skyblue']} fn={()=>console.log('props传递的函数')} tag={<p>props传递的标签</p>}/>,document.getElementById('root'))

  • props是只读的对象,只能读取属性的值,无法修改对象
  • 注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到props
import React, { Component } from 'react'

export default class Hello extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div>
        {/* <div>抽离类组件Hello</div> */}
        <div>props:{this.props.age}</div>
      </div>
    )
  }
}

组件通讯的三种方式

父传子

  1. 父组件提供需要传递的state数据
  2. 给子组件标签添加属性,值为state中的数据
  3. 子组件中通过props接受父组件中传递的数据
class Parent extends React.Component{
  state={
    lastName:'Mgd'
  }
  render() {
    const {lastName} = this.state;
    return (
      <div>
        我是父组件
        <Child name={lastName}></Child>
      </div>
    )
  }
}
class Child extends React.Component{
  state={}
  render(){
    return (
      <div>
        我是子组件
        <p>子组件接收到的父组件的数据:{this.props.name}</p>
      </div>
    )
  }
}
ReactDOM.render(<Parent/>,document.getElementById('root'))

子传父

  1. 父组件中提供一个回调函数(用于接受数据)
  2. 将该函数作为属性的值,传递给子组件
  3. 子组件通过props调用回调函数
  4. 将子组件的数据作为参数传递给回调函数
class Parent extends React.Component{
  state={
    childTxt:""
  }
  // 提供用于接受数据的回调函数
  getChildMsg = (msg)=>{
    console.log('子组件传递的数据:',msg);
    this.setState({
      childTxt:msg
    })
  }
  render() {
    const {childTxt} = this.state;
    return (
      <div>
        我是父组件
        <Child getMsg={this.getChildMsg}></Child>
        子组件传给父组件的值是:{childTxt}
      </div>
    )
  }
}
class Child extends React.Component{
  state={
    childMsg:'lxh'
  }
  handleClick= ()=>{
    this.props.getMsg(this.state.childMsg)
  }
  render(){
    return (
      <div>
        我是子组件
        <button onClick={this.handleClick}>传数据给父组件</button>
      </div>
    )
  }
}
ReactDOM.render(<Parent/>,document.getElementById('root'))

兄弟组件传值

  • 将共享状态提升到最近的公共组件中,由公众父组件管理这个状态
  • 公共父组件职责:1.提供共享状态 2. 提供操作共享状态的方法
  • 要通讯的子组件只需要通过props接收状态或操作状态的方法
class Counter extends React.Component{
  // 提供共享状态
  state={
    count:0
  }

  countAdd= ()=>{
    this.setState({
      count:this.state.count+1
    })
  }

  render() {
    const {count} = this.state;
    return (
      <div>
        <CountTxt count={count}/>
        <Add countAdd = {this.countAdd}/>
      </div>
    )
  }
}

class Add extends React.Component{
  handleClick = ()=>{
    this.props.countAdd()
  }
  render(){
    return (
      <div>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

class CountTxt extends React.Component{
  render() {
    return (
      <div>
        <h1>计数器:{this.props.count}</h1>
      </div>
    )
  }
}

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

Context

  • 作用:跨组件传递数据
  • 步骤:
  1. 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
  2. 使用Provider组件作为父节点
  3. 设置value属性,表示要传递的数据
  4. 调用Consumer组件接受数据
// 1.
const {Provider,Consumer} = React.createContext()
class Fatcher extends React.Component{
  state={
    obj:{
      name:'Mgd',
      id:1
    }
  }
  render() {
    return (
      // 2. 3.
      <Provider value={this.state.obj}>
        <div>
        我是Fatcher组件
          <Child/>
        </div>
      </Provider>
    )
  }
}

class Child extends React.Component{
  render() {
    return (
      <div>
        <SubChild/>
      </div>
    )
  }
}

class SubChild extends React.Component{
  render() {
    return (
      <div>
        <LastChild/>
      </div>
    )
  }
}
class LastChild extends React.Component{
  render() {
    return (
      <div>
        <p>这是Fatcher组件传递的值:
          {/* 4. */}
          <Consumer>
            {data=>{
              return (
                <span>
                  {data.name}
                </span>
              )
            }}
          </Consumer>
        </p>
      </div>
    )
  }
}
ReactDOM.render(<Fatcher/>,document.getElementById('root'))

props深入

children属性

  • children属性,表示组件标签的子节点。当组件标签有子节点时,props就有该属性
  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件、函数)
    children为文本节点

class App extends React.Component{
  render() {
    return (
      <div>
        <h1>
          我是组件标签的子节点:
          {this.props.children}
        </h1>
      </div>
    )
  }
}
ReactDOM.render(<App>我是子节点</App>,document.getElementById('root'))

children为React元素

class App extends React.Component{
  render() {
    return (
      <div>
        <h1>
          我是组件标签的子节点:
          {this.props.children}
        </h1>
      </div>
    )
  }
}

ReactDOM.render(<App><div>我是子节点</div></App>,document.getElementById('root'))

children为组件

class App extends React.Component{
  render() {
    return (
      <div>
        <h1>
          我是组件标签的子节点:
          {this.props.children}
        </h1>
      </div>
    )
  }
}

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

children为函数

class App extends React.Component{
  
  render() {
    this.props.children()
    return (
      <div>
        <h1>
          我是组件标签的子节点:
        </h1>
      </div>
    )
  }
}
ReactDOM.render(<App>{()=>console.log('children传递的函数')}</App>,document.getElementById('root'))

props校验

  • 允许在创建组件的时候,就指定props的类型、格式等。
  • 作用:捕获使用组件时因为props导致的错误,给出明确的错误指示,增加组件的健壮性
  • 使用步骤:
  1. 安装包prop-types
yarn add prop-types
或者
npm i prop-types
  1. 导图prop-types
  2. 使用组件名.propTypes = {}来给组件的props添加校验规则
  3. 校验规则通过PropTypes对象来指定
import propTypes from 'prop-types'
class App extends React.Component{
  render() {
    const {color}=this.props 
    return (
      <div>
        <ul>
          {
            color.map((item,index)=>{
              return (
                <li key={index}>{item}</li>
              )
            })
          }
        </ul>
      </div>
    )
  }
}
App.propTypes = {
  color:propTypes.array
}

ReactDOM.render(<App color={['red','blue']}/>,document.getElementById('root'))
  • 约束规则
  1. 常见类型:arrayboolfuncnumberobjectstring
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定结构对象:shape({ })
    https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
class App extends React.Component{
  render() {
    return (
      <div></div>
    )
  }
}
App.propTypes = {
  a:propTypes.number,
  fn:propTypes.func.isRequired,
  tag:propTypes.element,
  filter:propTypes.shape({
    area:propTypes.string,
    price:propTypes.number
  })
}
ReactDOM.render(<App fn={()=>{}}/>,document.getElementById('root'))

props默认值

  • 作用:给props设置默认值,在未传入props时生效
class App extends React.Component{
  render() {
    return (
      <div>
        props的默认值是:{this.props.color}
      </div>
    )
  }
}
// 添加props默认值  
App.defaultProps = {
  color:'red'
}

ReactDOM.render(<App fn={()=>{}}/>,document.getElementById('root'))

组件的生命周期

组件生命周期概述

  • 意义:有助于理解组件的运行方式,完成更加复杂的组件功能、分析组件错误原因
  • 组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
  • 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
  • 钩子函数的作用:未开发人员在不同阶段操作组件提供了时机
  • 只有类组件才有生命周期

生命周期的三个阶段

React生命周期的三个阶段

创建时(挂载阶段)

  • 执行时机:组件创建时(页面加载时)
  • 执行顺序: constructor()->render()->componentDidMount()
钩子函数触发时机作用
constructor()创建组件时,最先执行1.初始化state 2. 为事件处理程序绑定this
render()每次组件渲染都会触发渲染UI(注意:不能调用setState())
componentDidMount()组件挂载(完成DOM渲染)后1.发送网络请求 2.DOM操作
class App extends React.Component{
  constructor(props) {
    super(props)
    console.warn('钩子函数constructor被执行');
    // 初始化state
    this.state = {
      count:0
    }
    // 处理事件程序this指向
    // ...
  }
  componentDidMount() {
    console.warn('钩子函数componentDidMount被执行');
    // 获取DOM
    const el = document.getElementById('doudou')
    console.log(el);
    // 发送ajax请求
    // ...
  }
  render() {
    console.warn('render');
    return (
      <div>
        <h1 id='doudou'>豆豆挨打的次数:</h1>
        <button id='btn' >打豆豆</button>
      </div>
    )
  }
}
ReactDOM.render(<App color='blue'/>,document.getElementById('root'))

更新时(更新阶段)

  • 执行时机:1. setState() 2. forceUpdate() 3. 组件接受新的props
  • 说明: 以上三者任意一种变化,组件就会重新渲染
class Content extends React.Component{
  render() {
    console.warn('--子组件--钩子函数render被执行');

    return (
      <h1 id='doudou'>豆豆挨打的次数:{this.props.count}</h1>
    )
  }
}
Content.propTypes = {
  count:propTypes.number
}


class App extends React.Component{
  constructor(props) {
    super(props)
    console.warn('钩子函数constructor被执行');
    // 初始化state
    this.state = {
      count:0
    }
    // 处理事件程序this指向
    // ...
  }
  componentDidMount() {
    console.warn('钩子函数componentDidMount被执行');
    // 获取DOM
    const el = document.getElementById('doudou')
    console.log(el);
    // 发送ajax请求
    // ...
  }
  handleClick = () =>{
    this.setState({
      count:this.state.count+1
    })

    // 组件强制更新
    // this.forceUpdate()
  }
  render() {
    console.warn('钩子函数render被执行');
    const {count} = this.state;
    return (
      <div>
        <Content count={count}/>
        <button id='btn' onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}
ReactDOM.render(<App color='blue'/>,document.getElementById('root'))

卸载时(卸载阶段)

  • 执行时机:组件从页面中消失
钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作

render-props和高阶组件

React组件复用

  • 处理方式:复用相似的功能
  • 复用什么? 1. state 2. 操作state的方法
  • 两种方式:1. render-props 2. 高阶组件(HOC)
  • 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

render-props模式

  • 使用步骤
  1. 创建组件,在组件中提供复用的状态逻辑代码(a.状态 b.操作状态的方法)
  2. 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
    3.使用props.render()的返回值作为要渲染的内容
    index.js
import Mouse from './view/Mouse/Mouse'

class App extends React.Component{
  renderText= (mouse) =>{
    return (
      <p>鼠标x:{mouse.x}.鼠标y:{mouse.y}</p>
    )
  }
  rednerImg = mouse =>{
    return (
      <img src={img} alt="logo" style={{
        position:'absolute',
        top:mouse.y-96,
        left:mouse.x-96
      }}/>
    )
  }
  render() {
    return (
      <div>
        <Mouse render={this.renderText}/>
        <Mouse render={this.rednerImg}/>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

Mouse.js

import React, { Component } from 'react'

export default class Mouse extends Component {
  // 鼠标位置
  state = {
    x:0,
    y:0
  }

  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove',this.handleMouseMove)
  }

  // 鼠标移动事件处理程序 
  handleMouseMove = e =>{
    this.setState({
      x:e.clientX,
      y:e.clientY
    })
  }

  render() {
    return this.props.render(this.state)
  }
}

children代替render属性

  • 注意:并不是该模式叫render-props就必须使用名为renderprops,实际上可以使用任意名称的props
  • prop是一个函数并且告诉组件要渲染什么内容的技术叫做render props模式
  • 推荐使用children代替render属性
    index.js
import Mouse from './view/Mouse/Mouse'
class App extends React.Component{
  renderText= (mouse) =>{
    return (
      <p>鼠标x:{mouse.x}.鼠标y:{mouse.y}</p>
    )
  }
  rednerImg = mouse =>{
    return (
      <img src={img} alt="logo" style={{
        position:'absolute',
        top:mouse.y-96,
        left:mouse.x-96
      }}/>
    )
  }
  render() {
    return (
      <div>
        {/* <Mouse render={this.renderText}/>
        <Mouse render={this.rednerImg}/> */}

        {/* 使用children代替render */}
        <Mouse>
          {this.renderText}
        </Mouse>
        <Mouse>
          {this.rednerImg}
        </Mouse>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

Mouse.js

import { Component } from 'react'

export default class Mouse extends Component {
  // 鼠标位置
  state = {
    x:0,
    y:0
  }
  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove',this.handleMouseMove)
  }

  // 鼠标移动事件处理程序 
  handleMouseMove = e =>{
    this.setState({
      x:e.clientX,
      y:e.clientY
    })
  }

  render() {
    
    // return this.props.render(this.state)
    // 使用children代替render
    return this.props.children(this.state)

  }
  componentWillUnmount(){
    window.removeEventListener('mousemove',this.handleMouseMove)
  }
}

代码优化

  1. 推荐:给render props模式添加props校验
  2. 组件卸载时解除mousemove事件绑定

Mouse.js

import { Component } from 'react'
import propTypes from 'prop-types'
export default class Mouse extends Component {
  // 鼠标位置
  state = {
    x:0,
    y:0
  }
  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove',this.handleMouseMove)
  }

  // 鼠标移动事件处理程序 
  handleMouseMove = e =>{
    this.setState({
      x:e.clientX,
      y:e.clientY
    })
  }

  render() {
    
    // return this.props.render(this.state)
    // 使用children代替render
    return this.props.children(this.state)

  }
  componentWillUnmount(){
    window.removeEventListener('mousemove',this.handleMouseMove)
  }
}

Mouse.propTypes={
  children:propTypes.func.isRequired
}

高阶组件

  • 目的:实现状态逻辑复用
  • 高阶组件实际上是一个函数,接受要包装的组件,返回增强后的组件
  • 高阶组件内部会创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件
  • 使用步骤
  1. 创建一个函数,名称约定以with开头
  2. 指定函数的参数,参数应该以大写字母开头(作为要渲染的组件)
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  4. 在该组件中渲染参数组件,同时将状态通过prop传递给参数组件
  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
// 创建高阶组件 
function withMouse(WrappedComponent) {
  // 该组件提供复用的状态 
  class Mosue extends React.Component{
    // 鼠标状态  
    state = {
      x:0,y:0
    }
    handleMouse = e=>{
      this.setState({
        x:e.clientX,
        y:e.clientY
      })
    }
    // 控制鼠标状态逻辑  
    componentDidMount () {
      window.addEventListener('mousemove',this.handleMouse)
    }
    render() {
      return <WrappedComponent {...this.state }></WrappedComponent>
    }
    componentWillUnmount () {
      window.removeEventListener('mousemove',this.handleMouse)
    }
  }
  return Mosue
}



// 用来测试高阶组件
const Position = props=>{
  return (
    <p>鼠标位置x:{props.x},鼠标位置y:{props.y}</p>
  )
}

const PositionImg = props=>{
  return(
    <img src={img} alt="logo" style={{
      position:'absolute',
      top:props.y-96,
      left:props.x-96
    }}/>
  )
}

// 获取增强后的组件
const MousePositon = withMouse(Position);

const MousePositionImg = withMouse(PositionImg);

class App extends React.Component{
  render () {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 渲染增强后的组件 */}
        <MousePositon/>
        <MousePositionImg/>
      </div>
    )
  }
}

ReactDOM.render(<App/>,document.getElementById('root'))
设置displayName
  • 使用高阶组件存在的问题,得到的两个组件名称相同
  • 原因:默认情况下,React使用组件名称作为displayName
  • 解决方式:为高阶组件设置displayName便于调试时区分不同的组件
  • displayName的作用:用于设置调试信息

// 创建高阶组件 
function withMouse(WrappedComponent) {
  // 该组件提供复用的状态 
  class Mouse extends React.Component{
    // 鼠标状态  
    state = {
      x:0,y:0
    }
    handleMouse = e=>{
      this.setState({
        x:e.clientX,
        y:e.clientY
      })
    }
    // 控制鼠标状态逻辑  
    componentDidMount () {
      window.addEventListener('mousemove',this.handleMouse)
    }
    render() {
      return <WrappedComponent {...this.state }></WrappedComponent>
    }
    componentWillUnmount () {
      window.removeEventListener('mousemove',this.handleMouse)
    }
  }
  // 为高阶组件设置displayName
  Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
  return Mouse
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}



// 用来测试高阶组件
const Position = props=>{
  return (
    <p>鼠标位置x:{props.x},鼠标位置y:{props.y}</p>
  )
}

const PositionImg = props=>{
  return(
    <img src={img} alt="logo" style={{
      position:'absolute',
      top:props.y-96,
      left:props.x-96
    }}/>
  )
}

// 获取增强后的组件
const MousePositon = withMouse(Position);

const MousePositionImg = withMouse(PositionImg);

class App extends React.Component{
  render () {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 渲染增强后的组件 */}
        <MousePositon/>
        <MousePositionImg/>
      </div>
    )
  }
}

ReactDOM.render(<App/>,document.getElementById('root'))
传递props
  • 问题:props丢失
  • 原因:高阶组件没有往下传递props
// 创建高阶组件 
function withMouse(WrappedComponent) {
  // 该组件提供复用的状态 
  class Mouse extends React.Component{
    // 鼠标状态  
    state = {
      x:0,y:0
    }
    handleMouse = e=>{
      this.setState({
        x:e.clientX,
        y:e.clientY
      })
    }
    // 控制鼠标状态逻辑  
    componentDidMount () {
      window.addEventListener('mousemove',this.handleMouse)
    }
    render() {
      // 展开state和props
      return <WrappedComponent {...this.state } {...this.props}></WrappedComponent>
    }
    componentWillUnmount () {
      window.removeEventListener('mousemove',this.handleMouse)
    }
  }
  // 为高阶组件设置displayName
  Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
  return Mouse
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}



// 用来测试高阶组件
const Position = props=>{
  return (
    <p>鼠标位置x:{props.x},鼠标位置y:{props.y}</p>
  )
}

const PositionImg = props=>{
  return(
    <img src={img} alt="logo" style={{
      position:'absolute',
      top:props.y-96,
      left:props.x-96
    }}/>
  )
}

// 获取增强后的组件
const MousePositon = withMouse(Position);

const MousePositionImg = withMouse(PositionImg);

class App extends React.Component{
  render () {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 渲染增强后的组件 */}
        <MousePositon a={1}/>
        <MousePositionImg name='Mgd'/>
      </div>
    )
  }
}

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

React原理揭秘

setState()说明

更新数据

  • setState()更新数据是异步的
    *注意:使用该语法时,后面的setState()不要依赖于前面的setState()
  • 可以调用多次setState(),但是只会触发一次重新渲染
class App extends React.Component{
  state = {
    count:0
  }
  handleClick = ()=>{
    // setState是异步更新
    this.setState({
      count:this.state.count+1
    })
    console.log(this.state.count);
    this.setState({
      count:this.state.count+1
    })
  }
  render() {
    console.log('render');
    const {count} = this.state;

    return (
      <div>
        <h1>计数器:{count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

推荐语法

  • 推荐:使用setState((state,props)=>{})语法
  • 参数state:表示最新的state
  • 参数props:表示最新的props
class App extends React.Component{
  state = {
    count:0
  }
  handleClick = ()=>{
    // 推荐语法:也是异步更新setState
    this.setState((state,props)=>{
      return {
        count:state.count+1
      }
    })
    
    this.setState((state,props)=>{
      console.log('第二次调用:',state);
      return {
        count:state.count+1
      }
    })

    console.log(this.state.count);
  }
  render() {
    console.log('render');
    const {count} = this.state;

    return (
      <div>
        <h1>计数器:{count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

第二个参数

  • 场景:状态更新后(页面完成重新渲染)后立即执行某个操作
  • 语法: setState(updater[,callback])
class App extends React.Component{
  state = {
    count:0
  }
  handleClick = ()=>{
    // 推荐语法:也是异步更新setState
    this.setState(
      (state,props)=>{
        return {
          count:state.count+1
        }
      },
      ()=>{
        console.log(this.state.count);
      }
    )
  }
  render() {
    console.log('render');
    const {count} = this.state;

    return (
      <div>
        <h1>计数器:{count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

JSX语法的转换过程

  • JSXcreateElement()方法的语法糖(简化语法)
  • JSX语法被@bable/preset-react插件编译为createElement()方法
  • React是一个对象,用来描述希望在屏幕上看到的内容
const elememt = <h1 className='green'>Hello JSX</h1>
console.log(elememt);
ReactDOM.render(elememt,document.getElementById('root'))

组件更新机制

  • setState()的作用:1.修改state 2.更新组件
  • 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

组件性能优化

减轻state

  • 减轻state:只存储跟组件渲染相关的数据
  • 注意:不用做渲染的数据不要放在state
  • 对于这种需要在多个方法中用到的数据,应该放在this
class Content extends React.Component{
  componentDidMount() {
    this.myTimer = setInterval(()=>{
      console.log("定时器执行");
    },500)
  }
  render() {
    console.warn('--子组件--钩子函数render被执行');
    return (
      <h1></h1>
    )
  }

  componentWillUnmount() {
    console.warn('--子组件--钩子函数componentWillUnmount被执行');
    clearInterval(this.myTimer)
  }
}

避免不必要的重复渲染

  • 组件更新机制:父组件更新会引起子组件也被更新
  • 问题:子组件没有任何变化时也会重新渲染
  • 如何避免不必要的重新渲染呢?
  • 解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)
  • 作用:通过钩子函数的返回值决定该组件是否重新渲染,返回true表示更新渲染,false表示不重新渲染
  • 触发机制:更新阶段的钩子函数,组件重新渲染执行(shouldComponentUpdate–>render)
class App extends React.Component{
  state = {
    count:0
  }
  handleClick = ()=>{
    // 推荐语法:也是异步更新setState
    this.setState(
      (state,props)=>{
        return {
          count:state.count+1
        }
      }
    )
  }
  shouldComponentUpdate(nextProps,nextState) {
    console.log('最新的state:',nextState);
    console.log('this.state',this.state);
    return true;
  }
  render() {
    console.log('render','组件更新');
    const {count} = this.state;
    return (
      <div>
        <h1 id='title'>计数器:{count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}


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

demo: 随机数

class App extends React.Component{
  state = {
    number:0
  }
  handleClick = ()=>{
    this.setState((state,props)=>{
      return {
        number:Math.floor(Math.random()*3)
      }
    })
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('最新状态',nextState,'---当前状态',this.state);
    return nextState.number!==this.state.number;
  }
  render() {
    console.log('render');
    const {number} = this.state;
    return (
      <div>
        <h1>随机数:{number}</h1>
        <button onClick={this.handleClick}>重新生成</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

纯组件

  • 说明:纯组件内部的对比是shallow compare(浅层对比)
  • 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
  • 对于引用类型来说:只比较对象的引用(地址)是否相同
  • 注意:stateprops中属性值为引用类型时,应该创建新数据,不要直接修改原数据
class App extends React.PureComponent{
  state = {
    obj:{
      number:0
    }
  }
  handleClick = ()=>{
    // 正确的做法 
    const newObj = {...this.state.obj,number:Math.floor(Math.random()*3)}
    this.setState(()=>{
      return{
        obj:newObj
      } 
    })
  }
  render() {
    console.log('render');
    return (
      <div>
        <h1>随机数:{this.state.obj.number}</h1>
        <button onClick={this.handleClick}>重新生成</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

虚拟DOM和Diff算法

  • React更新视图的思想是:只要state状态发生变化就重新渲染视图
  • 问题:组建中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是
  • 理想状态:部分更新,只更新变化的地方

执行过程

  1. 初次渲染时,React会根据初试state(Model),创建一个虚拟DOM对象
  2. 根据虚拟DOM生成真正的DOM,渲染到页面中
  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象
  4. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
  5. 最终,React之将变化的内容更新(patch)到DOM中,重新渲染页面

虚拟DOM的最大价值从来都不是性能,而实让代码脱离了浏览器运行环境束缚,只要能运行js,就能运行React

React路由基础

介绍

  • 前端路由的功能:让用户从一个视图导航到另一个视图
  • 前端路由是一套映射规则,在React中,是URL路径,与组件的对应关系
  • 使用React路由简单来说,就是配置路径和组件

基本使用

使用步骤

  1. 安装:yarn add react-router-dom
  2. 导入路由的核心组件:Router/Route/Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
  1. 使用Router组件包裹整个应用
render(){
    return (
      <Router>
        <div>
          <h1>React路由</h1>
        </div>
      </Router>
      
    )
}
  1. 使用Link组件作为导航菜单(路由入口)
render(){
    return (
      <Router>
        <div>
          <h1>React路由</h1>
          {/* 指定路由入口 */}
          <Link to='/first'>页面1</Link>
        </div>
      </Router>
      
    )
}
  1. 使用Route组件配置路由规则和要展示的组件(路由出口)
const First = ()=> <p>这是页面1的内容</p>

class App extends React.Component{
  render(){
    return (
      <Router>
        <div>
          <h1>React路由</h1>
          {/* 指定路由入口 */}
          <Link to='/first'>页面1</Link>
          <Route path='/first' component={First}></Route>
        </div>
      </Router>
      
    )
  }
}

常用组件说明

  • Router组件:包裹整个应用,一个React应用是需要使用一次
  • 两种常用Router:HashRouterBrowerRouter
  • HashRouter:使用URL的哈希值实现
  • BrowerRouter:使用H5historyAPI实现
  • Link组件:用于指定导航链接
  • Route组件:指定路由展示相关组件信息

路由的执行过程

  1. 点击Link组件,修改了浏览器地址栏中的url
  2. React路由监听到地址栏url的变化
  3. React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行交配
  4. 当路由规则path能够匹配到地址栏中的pathname时,就展示该Route组件的内容
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

const First = ()=> <p>这是First页面的内容</p>
const Home = ()=> <p>这是Home页面的内容</p>

class App extends React.Component{
  render(){
    return (
      <Router>
        <div>
          <h1>React路由</h1>
          {/* 指定路由入口 */}
          <Link to='/first'>页面1</Link>
          <Link to='/home'>页面2</Link>
          
          <Route path='/first' component={First}></Route>
          <Route path='/home' component={Home}></Route>
        </div>
      </Router>
      
    )
  }
}
ReactDOM.render(<App/>,document.getElementById('root'))

编程式导航

  • 编程式导航:通过JS代码来实现页面跳转
  • historyReact路由提供的,用于获取浏览器历史记录的相关信息
  • push(path):跳转到某个页面,参数path表示要跳转的路径
  • go(n):前进或后退到某个页面,参数n表示前进或后退页面数量
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
class Login extends React.Component {
  
  handleClick= ()=>{
    this.props.history.push('/home')
  }
  render() {
    return (
      <div>
        <p>登录页面</p>
        <button onClick = {this.handleClick}>登录</button>
      </div>
    )
  }
}

class Home extends React.Component {
  handleClick = ()=>{
    this.props.history.go(-1)
  }
  render() {
    return (
      <div>
        <h2>我是Home页面</h2>
        <button onClick={this.handleClick}>返回Login</button>
      </div>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <h1>编程式导航</h1>
          <Link to='/login'>去登录页面</Link>
          <Route path='/login' component={Login}></Route>
          <Route path='/home' component={Home}></Route>

        </div>
      </Router>

    )
  }
}

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

默认路由

  • 默认路由:表示进入页面时就会匹配的路由
  • 默认路由path为:/
<Route path='' component={Home}></Route>

匹配模式

模糊匹配模式

  • 问题:当Link组件的to属性为’/login’时,为什么默认路由也被匹配
  • 默认情况下, React的路由是模糊匹配模式
  • 模糊匹配规则:只要pathnamepath开头就会匹配成功
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
const Home = ()=> <p>进入页面时,你能看到我吗</p>

const Login = ()=><p>我是Login组件的内容</p> 

const App = ()=>(
  <Router>
    <div>
      <h1>默认路由</h1>
      <Link to='/login'>登录页面</Link>

      <Route path='/' component={Home}></Route>
      <Route path='/login' component={Login}></Route>
    </div>
  </Router>
)
ReactDOM.render(<App />, document.getElementById('root'))

精确匹配

  • Route组件添加一个exact属性,让其变为精确匹配模式
  • 精确匹配:只有当pathpathname完全匹配时才会展示该路由
<Route exact path='/' component={Home}></Route>

React路由配置 DEOM

  1. src下创建route/routes.js
import Home from '../view/Home'
import List from '../view/List'
import News from '../view/News'
import My from '../view/My'
import UserAdd from '../components/user/UserAdd'
import UserList from '../components/user/UserList'
const routes = [
  {
    path:'/',
    component:Home,
    name:'Home',
    exact:true
  },
  {
    path:'/list',
    component:List,
    name:'List',

  },
  {
    path:'/news',
    component:News,
    name:'News',

  },
  {
    path:'/my',
    component:My,
    name:'My',
    route:[
      {
        path:'/my/useradd',
        component:UserAdd,
        name:'UserAdd',
      },
      {
        path:'/my/surliest',
        component:UserList,
        name:'UserList',
      },
      
    ]
  },
]
export default routes;
  1. src下的App.js内配置路由导航
import logo from './logo.svg';
import './App.css';
import routes from './route/routes'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

function App() {
  return (
    <Router>
      <div className="App">
        {
          routes.map((item, index) => (
            <Link to={item.path} key={index}>{item.name}</Link>
          ))
        }
        {
          routes.map((item, index) => {
            if (item.exact) {
              return (
                <Route exact path={item.path} component={item.component} key={index}>
                  {
                    <item.component routes={item.route} />
                  }
                </Route>
              )
            }
            return (
              <Route path={item.path} component={item.component} key={index}>
                {
                  <item.component routes={item.route} />
                }
              </Route>
            )
          })
        }
      </div>
    </Router>
  );
}

export default App;
  1. 假设有子路由,eg:routes.js内的My,需要在My/index.js配置子路由
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import './index.css'
export default class index extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <h1>My组件</h1>
        <div className=''>
          {
            this.props.routes.map((item, index) => (
              <Link to={item.path} key={index}>{item.name}</Link>
            ))
          }
        </div>
        <div className=''>
          {
            this.props.routes.map((item, index) => (
              <Route exact path={item.path} component={item.component} key={index}></Route>
            ))
          }
        </div>
      </div>
    )
  }
}
  1. src/index.js内引入./App.js,并渲染
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值