从零开始的React学习(一)

React基础

写在前面:这是我学习B站黑马React视频前75p所作的笔记。
后续的学习也会整理出笔记,供自己之后翻阅,也希望能给你带来一些帮助。

(1)概述

​ 用于构建用户界面的JavaScript库

(2)特点

  • 基于组件
  • 学习一次,随处使用

(3)基本安装与使用

  1. 安装命令

    npm i react react-dom
    
  2. 使用

    // 1.导入react和react-dom文件
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    // 2.创建react元素
    const title = React.createElement('h1', null, 'hello world!')
    
    // 3.渲染react元素
    ReactDOM.render(title, document.getElementById('root'))
    
    

(4)脚手架安装与使用

  1. 意义

    • 脚手架是开发现代Web应用的必备
    • 充分利用Webpack、Babel、ESLint等工具辅助项目开发
    • 零配置,无需手动配置繁琐的工具即可使用
    • 关注业务,而不是工具配置
  2. 初始化

    npx create-react-app my-app
    
    npx 是npm v5.2.0 引入的一条命令
    目的是提升包内提供的命令行工具的使用体验;
    原来需要先安装脚手架包,再使用这个包中提供的命令;现在无需安装脚手架包,可以直接使用这个包中提供的命令。
    
  3. 使用

    同上。

    启动命令
    yarn start 或 npm start
    

(5)React Developer Tools 相关问题

这是我个人遇到的一些相关问题:

  1. 安装:不能科学上网时chrome商店打不开,就度娘找找资源,下载然后拖到浏览器扩展应用里会自动安装。
  2. 不能动态显示数据:我的原因是自己在 component里的Hide components where…加了过滤,去掉过滤就好
  3. 控制台可能有警告(强迫症难受):把浏览器的迅雷下载组件删了,或者是把浏览器F12的setting下的Preferences里的 Enable JavaScript source maps 和 Enable CSS source maps取消勾选即可
  4. 插件不可用:在扩展应用的React Developer Tools 的详情里,打开 “允许访问文件网址”

一、基础

(1)JSX的基本使用

​ 用React.createElement()繁琐,不直观,不优雅。

JSX简介

JSX是React的核心内容。是JavaScript XML简写。优势是声明式的语法,更加直观,与HTML结构相同,降低学习成本,提高开发效率。

1.使用步骤
/* 
  使用JSX创建React元素 
*/
const title = (
   <h1 className="title">
   hello JSX!
   <span>这是span</span>
   </h1>
)
// 渲染react元素
const root = document.getElementById('root')
ReactDOM.render(title, root)

为什么脚手架中可以使用JSX语法?

  • JSX不是标准的ECMAScript语法,是ECMAScript的语法扩展

  • 需要使用babel编译处理后才能在浏览器环境使用

  • create-react-app脚手架中已经默认有该配置,无需手动配置

  • 编译JSX语法的包:@babel/preset-react

2.注意点
  • React元素的属性使用驼峰命名法
  • 特殊属性名: class -> className; for -> htmlFor; tabindex -> tabIndex
  • 没有子节点的React元素可以用 /> 结尾
  • 推荐:使用小括号包裹JSX,从而避免JS中自动插入分号陷阱
3.JSX中使用JavaScript表达式
/* 
  JSX中使用JavaScript表达式
*/
const name = 'Tom'
const age = 19
const title = (
  <h1>
    hello {name} ! 年龄: {age}
  </h1>
)
ReactDOM.render(title, document.getElementById('root'))

/* 
  条件渲染
*/
const isLoading = false
const loadData = () => {
  if (isLoading) {
    return (<div>loading...</div>)
  }
  return (<div>数据加载完成,此处显示加载后的数据</div>)
}

// 三元表达式
const loadData = () => {
  return isLoading ? (<div>
    loading...
  </div>) : (<div>
    数据加载完成
  </div>)
}

// 逻辑运算符
const loadData = () => {
  return isLoading && (<div>
    loading...
  </div>)
}

const load = (
  <h1>
    条件渲染:
     {loadData()}
  </h1>
)
ReactDOM.render(load, document.getElementById('root'))
4.JSX列表渲染
/* 
    列表渲染
*/
const songs = [
  { id: 1, name: '痴心绝对'},
  { id: 2, name: '阿拉斯加的海湾'},
  { id: 3, name: '长安姑娘'},
]

const list = (
  <ul>
      {songs.map(item => {
          return (
              <li key={item.id}>{item.name}</li>
          )
      })}
  </ul>
)

ReactDOM.render(list, document.getElementById('root'))
5.JSX样式处理
// 类名,外部引入
import './index.css'

// 行内样式
const list = (
  <h1 className="title" style={{color: 'skyblue', backgroundColor: 'pink'}}>
    JSX的样式处理
  </h1>
)

ReactDOM.render(list, document.getElementById('root'))
总结
  1. JSX是React的核心内容
  2. JSX表示在JS代码中写HTML结构,是React声明式的体现
  3. 使用JSX配合嵌入的JS表达式、条件渲染、列表渲染、可以描述任意UI结构
  4. 推荐使用类名给JSX添加样式
  5. React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能

(2)React组件

组件介绍
  • 组件是React的一等公民,使用React就是在用组件
  • 组件表示页面中的部分功能
  • 组合多个组件实现完整的页面功能
  • 特点:可复用、独立、可组合
1.创建方式
  • 使用函数创建
  • 使用类创建
  • 抽离为独立的JS文件
2.React事件处理
import React from 'react'

/* 
    React事件处理
*/
// 使用类组件形式
 class App extends React.Component {
     // 事件处理程序
     handleClick() {
         console.log('单击事件触发了')
     }
     render() {
         return (
             <button type="" onClick={this.handleClick}>点我</button>
         )
     }
 }

// 使用函数组件形式
function App() {
    function handleClick() {
        console.log('函数组件的点击事件被触发了')
    }

    return (
        // 和类组件相比,就没有this,可以直接通过函数名
        <button type="" onClick={handleClick}>函数组件-点我</button>
    )
}

export default App
3.React事件对象
import React from 'react'
/*     
	React事件对象 (合成事件)
*/
// 使用类组件形式
class App extends React.Component {    
	// 事件处理程序    
	handleClick(e) {        
		// 阻止默认行为        
		e.preventDefault()        
		console.log('a标签单击事件触发了')        
		console.log('事件对象:', e)    
	}   
	render() {        
		return (            
				<a href="https://www.bilibili.com" onClick={this.handleClick}>B站</a>        
			)    
		}}
export default App
4.有状态组件/无状态组件
import React from 'react'

/* 
    React有无状态组件
*/
// 函数组件又叫做无状态组件,类组件又叫做有状态组件
// 状态(state)即数据
// 函数组件没有自己的状态,只负责数据展示(静)
// 类组件有自己的状态,负责更新UI,让页面“动起来”

/* 
    state的基本使用
*/
// 状态是可以改变的,通过setState({要修改的数据})来修改数据;不能直接修改state中的值
// setState() 1.修改状态  2.更新UI
// 数据驱动的思想

// 使用类组件形式
class App extends React.Component {
    // constructor() {
    //     super()
    //     // 初始化状态
    //     this.state = {
    //         count: 0
    //     }
    // }

    // 简化语法初始化state
    state = {
        count: 0,
        test: 'a'
    }

    // render() {
    //     return (
    //         <div>
    //             <h1>计数器:{this.state.count}</h1>
    //             <button type="" onClick={
    //                 () => {
    // 这里的this指向外部环境,即render()方法,就是当前组件的实例,所以可以正常执行
    //                     this.setState({
    //                         count: this.state.count + 1
    //                     })
    //                 }
    //             }>+1</button>
    //         </div>
    //     )
    // }

    // 抽离逻辑代码

    // 解决this指向问题
    // 1.箭头函数
    // 2.Function.prototype.bind()
    // 3.class的实例方法(也是利用箭头函数this指向的特点)

    // 利用ES5的bind方法,将事件处理程序中的this与组件实例绑定到一起
    // constructor() {
        // constructor里的this也是指向实例
        // super()
        // state也可以放在这里
        // this.state = {
        //     count: 0
        // }
        // this.onIncrease = this.onIncrease.bind(this)
    // }
    // 事件处理程序
    // onIncrease() {
    //     this.setState({
    //         count: this.state.count + 1
    //     })
    // }
    // 第三种改变this指向的方法
    onIncrease = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <h1>计数器:{this.state.count}</h1>
                {/*箭头函数本身不绑定this,这里的this指向外部环境,即render()方法,就是当前组件的实例,所以可以正常执行 */}
                {/* <button type="" onClick={() => this.onIncrease()}>点我+1</button> */}
                <button type="" onClick={this.onIncrease}>+1</button>
            </div>
        )
    }
}

export default App
5.表单处理-受控组件
import React from 'react'

/* 
    React表单处理
*/
/* 
    1.受控组件:受到React控制的表单元素
    HTML中表单元素是可输入的,也就是有自己可变的状态,而React中可变状态通常保存在state中,并且只能通过setState()方法来修改;React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
*/
// 步骤
// 1.在state中添加一个状态,作为表单元素的value值
// 2.给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

// 问题:每个表单元素都有一个单独的事件处理程序
// 优化步骤
// 1.给表单元素添加name属性,名称与state相同
// 2.根据表单元素类型获取对应值(有些表单是value,有的是checked)
// 3.在change事件处理程序中通过[name]来修改对应的state

// 使用类组件形式
class App extends React.Component {
    // 简化语法初始化state
    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
        })
    }

    // 处理复选框
    // handleCheckBox = e => {
    //     this.setState({
    //         isChecked: e.target.checked
    //     })
    // }

    /* 
        事件处理程序优化
    */
    handleAllChange = e => {
        // 获取当前DOM对象
        const target = e.target

        // 根据类型判断获取值
        const value = target.type === 'checkbox' ? target.checked : target.value
        
        // 获取name
        const name = target.name

        this.setState({
            [name]: value
        })
    }

    render() {
        return (
            <div>
                {/* 文本框 */}
                {/* <input type="text" name="txt" value={this.state.txt} onChange={this.handleChange} /> */}
                <input type="text" name="txt" value={this.state.txt} onChange={this.handleAllChange} />
                
                {/* 富文本框 */}
                {/* <textarea rows="" cols="" name="content" value={this.state.content} onChange={this.handleContent}></textarea> */}
                <textarea rows="" cols="" name="content" value={this.state.content} onChange={this.handleAllChange}></textarea>

                {/* 下拉框 */}
                {/* <select name="city" value={this.state.city} onChange={this.handleCity}> */}
                <select name="city" value={this.state.city} onChange={this.handleAllChange}>
                    <option value="sh">上海</option>
                    <option value="bj">北京</option>
                    <option value="wh">武汉</option>
                </select>
                <br/>
                {/* 复选框 */}
                {/* <input name="isChecked" type="checkbox" checked={this.state.isChecked} onChange={this.handleCheckBox}/> */}
                <input name="isChecked" type="checkbox" checked={this.state.isChecked} onChange={this.handleAllChange}/>
            </div>
        )
    }
}

export default App
6.表单处理-非受控组件
import React from 'react'

/* 
    2.非受控组件(不推荐)
    借助ref,使用原生DOM方式来获取表单元素值
    ref的作用:获取DOM或组件
*/
// 使用步骤
// 1.使用React.createRef()方法创建一个ref对象
// 2.将创建好的ref对象添加到文本框中(关联表单元素)
// 3.通过ref对象获取到文本框的值

// 使用类组件形式
class App extends React.Component {
    constructor() {
        super()

        // 创建ref
        this.txtRef = React.createRef()
    }

    // 获取文本框的值
    getTxt = () => {
        console.log('文本框的值为:', this.txtRef.current.value)
    }

    render() {
        return (
            <div>
                <input type="text" ref={this.txtRef} />
                <button type="" onClick={this.getTxt}>获取文本框的值</button>
            </div>
        )
    }
}

export default App
总结
  1. 组件的两种创建方式:函数组件和类组件
  2. 无状态(函数)组件负责静态结构展示
  3. 有状态(类)组件负责更新UI,让页面动起来
  4. 绑定事件注意this指向问题
  5. 推荐使用受控组件来处理表单
  6. 完全利用JS语言的能力创建组件,这是React的思想

二、进阶

(1)React组件进阶

组件通讯介绍

组件是独立且封闭的单元,默认情况下只能使用组件自己的数据。组件化过程中多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。

1.组件的props
  • props作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
import React from 'react'
// import ReactDOM from 'react-dom'

/* 
    props
*/
// 2.函数组件接收数据
// const App = (props) => {
//     // props是一个对象
//     console.log(props)
//     return (
//         <div>
//             <h1>props: {props.name}</h1>
//         </div>
//     )
// }

// 类组件接收数据
class App extends React.Component {
    // 推荐使用props作为构造函数参数
    constructor(props) {
        super(props)
        console.log(props)
    }
    render() {
        return (
            <div>
                <h1>props: {this.props.name}</h1>
            </div>
        )
    }
}

// 1.传递数据
// ReactDOM.render(<App name="Tom" age={19}></App>, document.getElementById('root'))

特点

  • 可以给组件传递任意类型的数据(字符串“ ”,非字符串直接用 { } 包裹;可以传数字、数组、函数、jsx)
  • props是只读的对象,只能读取属性的值,无法修改对象
  • 使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到props
2.组件通讯的三种方式
2.1 父组件传给子组件
  1. 父组件提供要传递的state数据

  2. 给子组件标签添加属性,值为state中的数据

  3. 子组件中通过props接收父组件中传递的数据

import React from 'react'
/* 
    组件通讯的三种方式
*/
// 父组件传给子组件
// 父组件
class Parent extends React.Component {
    state = {
        lastName: "wang"
    }

    render() {
        return (
            <div className="parent">
                父组件:
                 <Child name={this.state.lastName}></Child>
            </div>
        )
    }
}

// 子组件
const Child = (props) => {
    return (
        <div className="child">
            <p>子组件接收到父组件的数据:{props.name}</p>
        </div>
    )
}

export default Parent
2.2 子组件传给父组件

**思路:**利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数。(和Vue是一样的)

**注意:**回调函数this指向的问题

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为属性的值,传递给子组件
  3. 子组件通过props调用回调函数
  4. 将子组件的数据作为参数传递给回调函数
import React from 'react'

/* 
    组件通讯的三种方式
*/

// 子组件传给父组件
// 父组件
class Parent extends React.Component {
    state = {
        parentMsg: ''
    }
    // 提供回调函数,用来接收数据
    getChildMsg = (data) => {
        console.log('接收到子组件中传递过来的数据:', data)
        this.setState({
            parentMsg: data
        })
    }

    render() {
        return (
            <div className="parent">
                父组件:{this.state.parentMsg}
                 <Child getMsg={this.getChildMsg}></Child>
            </div>
        )
    }
}

// 子组件
class Child extends React.Component {
    state = {
        msg: "呷哺呷哺"
    }
    handleClick = () => {
        // 子组件调用父组件传递过来的回调函数
        this.props.getMsg(this.state.msg)
    }
    render() {
        return (
            <div className="child">
                子组件:<button onClick={this.handleClick}>点我,给父组件传递数据</button>
            </div>
        )
    }
}

export default Parent
2.3 兄弟组件之间通讯

**思路:**将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态(状态提升

  • 公共父组件职责:1.提供共享状态;2.提供操作共享状态的方法
  • 要通讯的子组件只要通过props接收状态或操作状态的方法
import React from 'react'

/* 
    组件通讯的三种方式
*/

/* 
    兄弟组件之间通讯
*/
// 公共父组件
class Parent extends React.Component {
    // 提供共享状态
    state = {
        count: 0
    }

    // 提供修改共享状态的方法
    onIncrease = () => {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return (
            <div>
                <Child1 count={this.state.count}/>
                <Child2 onIncrease={this.onIncrease}/>
            </div>
        )
    }
}

// 子组件1
const Child1 = (props) => {
    return (
        <h1>计数器:{props.count}</h1>
    )
}

// 子组件2
const Child2 = (props) => {
    
    return (
        <button onClick={() => props.onIncrease()}>点我+1</button>
    )
}

export default Parent
3.Context

**作用:**跨组件传递数据(比如:主题、语言等)

使用步骤:

  1. 调用 React.createContext() 创建 Provider(提供数据)和 Consumer(消费数据)两个组件

  2. 使用 Provider 组件作为父节点

  3. 设置value,表示要传递的数据

  4. 使用Consumer组件接收数据

import React from 'react'

/* 
    Context
*/

// 1.创建context得到两个组件
const {Provider, Consumer} = React.createContext()

class App extends React.Component {
    render() {
        return (
            // 2.使用 Provider 组件作为父节点
            // 3.设置value,表示要传递的数据
            <Provider value="pink">
                <div>
                    <Node/>
                </div>
            </Provider>
        )
    }
}

const Node = (props) => {
    return (
        <div>
            我是Node节点
            <SubNode/>
        </div>
    )
}

const SubNode = (props) => {
    return (
        <div>
            我是SubNode节点
            <Child/>
        </div>
    )
}

const Child = (props) => {
    return (
        <div>
            我是Child节点
             {/* 4.使用Consumer组件接收数据 */}
            <Consumer>
                {
                    data => <h1>传递来的值 -- {data}</h1>
                }
             </Consumer>
        </div>
    )
}

export default App

总结:

  1. 如果两个组件是”远房亲戚“(比如多层嵌套)可以使用Context实现组件通讯
  2. Context提供了两个组件:Provider和Consumer
  3. Provider组件:用来提供数据
  4. Consumer组件:用来消费数据
4.props深入
4.1 children属性
  • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性

  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)

 /* 
     children属性
 */
 
 /* children为文本节点 */
 // const App = (props) => {
 //     console.log(props)
 //     return (
 //         <div>
 //             <h1>组件标签的子节点:</h1>
 //             {props.children}
 //         </div>
 //     )
 // }
 // ReactDOM.render(<App>我是子节点</App>, document.getElementById('root'))
 
 
 /* children为JSX或组件 */
 // const App = (props) => {
 //     console.log(props)
 //     return (
 //         <div>
 //             <h1>组件标签的子节点:</h1>
 //             {props.children}
 //         </div>
 //     )
 // }
 // ReactDOM.render(<App><p>我是子节点,是一个p标签</p></App>, document.getElementById('root'))
 
 // const Test = () => <button>我是按钮组件</button>
 // ReactDOM.render(<App><Test/></App>, document.getElementById('root'))
 
 /* children为函数 */
 const App = (props) => {
     console.log(props)
     return (
         <div>
             <h1>组件标签的子节点:</h1>
             {props.children()}
         </div>
     )
 }
 
 // ReactDOM.render(<App>{ () => console.log('这是一个函数子节点')}</App>, document.getElementById('root'))
4.2 props校验
  • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据

  • 如果传入的数据格式不对,可能会导致组件内部报错

  • 关键问题:组件的使用者不知道明确的错误原因

  • props校验:允许在创建组件的时候,就指定props的类型、格式等

  • 作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

    使用步骤:

    1. 安装包:prop-types (yarn add prop-types / npm i prop-types)

    2. 导入prop-types包

    3. 使用 组件名.propTypes = {} 来给组件的props添加校验规则

    4. 校验规则通过 PropTypes 对象来指定

      /* 
          props校验
      */
      // 导入
      import PropTypes from 'prop-types'
      
      const App = (props) => {
          const arr = props.colors
          const lis = arr.map((item, index) => {
              return (
                  <li key={index}>{item}</li>
              )
          })
          return (
              <ul>
                  {lis}
              </ul>
          )
      }
      
      // 添加props校验
      App.propTypes = {
          colors: PropTypes.array
      }
      
      // ReactDOM.render(<App colors={19}></App>, document.getElementById('root'))
      // 会有明确的错误提示
      // Warning: Failed prop type: Invalid prop `colors` of type `number` supplied to `App`, expected `array`.
      
      // ReactDOM.render(<App colors={['red', 'blue']}></App>, document.getElementById('root'))
      

    约束规则:

    1. 常见类型:array、bool、func、number、object、string

    2. React元素类型:element

    3. 必填项:isRequired

    4. 特定结构的对象:shape({ })

    import React from 'react'
    
    /* 
        props校验-约束规则
    */
    // 导入
    import PropTypes from 'prop-types'
    
    const App = (props) => {
        return (
            <div>
                <h1>props校验:</h1>
            </div>
        )
    }
    /* 
        添加props校验
        属性 a 的类型:数值(number)
        属性 fn 的类型:函数(func)并且为必填项
        属性 tag 的类型:React元素(element)
        属性 filter 的类型:对象({ area:'上海', price: 1999})
    */
    App.propTypes = {
        a: PropTypes.number,
        fn: PropTypes.func.isRequired,
        tag: PropTypes.element,
        filter: PropTypes.shape({
            area: PropTypes.string,
            price: PropTypes.number
        })
    }
    
    export default App
    ```
###### 4.3 props的默认值

- 场景:分页组件 → 每页显示条数
- 作用:给props设置默认值,在未传入props时生效

```jsx
import React from 'react'

/* 
   props的默认值
*/

const App = (props) => {
   console.log(props)
   return (
       <div>
           <h1>props的默认值:{props.pageSize}</h1>
       </div>
   )
}

// 添加props默认值
App.defaultProps = {
   pageSize: 10
}

// ReactDOM.render(<App pageSize={20}></App>, document.getElementById('root'))
// 这是显示的就是 20 而不是 10

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

    • 执行时机:组件创建时(页面加载时)

    • 执行顺序:constructor() → render() → componentDidMount()

      钩子函数触发时机作用
      constructor创建组件时,最先执行1.初始化state;2.为事件处理程序绑定this
      render每次组件渲染都会触发渲染UI(注意:不能调用setState()
      componentDidMount组件挂载(完成DOM渲染后)1.发送网络请求;2.DOM操作
import React from 'react'

/* 
    组件生命周期
*/
// 创建时(挂载阶段)
class App extends React.Component {
    constructor() {
        super()

        // 初始化state
        this.state = {
            count: 0
        }
        // 处理this绑定问题

        // const title = document.getElementById('title')
        // 此时DOM还没渲染,拿不到DOM
        // console.log(title) 结果:null
        console.warn('生命周期钩子函数: 1.constructor')
    }

    componentDidMount() {
        // const title = document.getElementById('title')
        // 1.此时已经完成DOM渲染,可以拿到DOM,进行DOM操作
        // console.log(title)

        // 2.发送AJAX请求,获取远程数据
        // axios.get('http://api....')
        
        console.warn('生命周期钩子函数: 3.componentDidMount')
    }

    render() {
        // 在render()里调用this.setState()会造成递归更新
        console.warn('生命周期钩子函数: 2.render')
        // 渲染返回内容
        return (
            <div>
                <h1 id="title">统计豆豆被打的次数:</h1>
                <button id="btn">打豆豆</button>
            </div>
        )
    }
}

export default App
  1. 更新时(更新阶段)

    • 执行时机:1.setState(); 2.forceUpdate(); 3.组件接收到新的props

    • 说明:以上三者任意一种变化,组件都会重新渲染

    • 执行顺序:render() → componentDidUpdate()

      钩子函数触发时机作用
      render每次组件渲染都会触发渲染UI(与 挂载阶段 是同一个render)
      componentDidUpdate组件更新(完成DOM渲染)后1.发送网络请求;2.DOM操作;注意:如果要setState()必须放在一个if条件中
      import React from 'react'
      
      /* 
          组件生命周期
      */
      // 更新时(更新阶段)
      class App extends React.Component {
          constructor(props) {
              super(props)
      
              // 初始化state
              this.state = {
                  count: 0
              }
          }
      
          handleClick = () => {
              // 1.调用setState,此时父组件会重新渲染
              this.setState({
                  count: this.state.count + 1
              })
      
              // 3.演示强制更新,即使属性没有更新也会强制更新
              // this.forceUpdate()
          }
      
          render() {
              return (
                  <div>
                      {/* 2.接收到新属性,子组件也会更新 */}
                      <Counter count={this.state.count}></Counter>
                      <button id="btn" onClick={this.handleClick}>打豆豆</button>
                  </div>
              )
          }
      }
      
      class Counter extends React.Component {
          render() {
              // 2.接收到新属性,子组件也会更新
              console.warn('子组件——生命周期钩子函数: render')
      
              return (
                  <h1 id="title">统计豆豆被打的次数:{this.props.count}</h1>
              )
          }
      
          componentDidUpdate(prevProps) {
              console.warn('子组件——生命周期钩子函数: componentDidUpdate')
      
              // 可以获取DOM
              // const title = document.getElementById('title')
              // console.log(title.innerHTML)
      
              // 注意:如果要调用setState()更新状态,必须要放在if条件中。否则也会造成递归更新
              // 原因:如果直接调用setState()更新状态,会再去执行render(),render()执行完会立即去执行componentDidUpdate(),从而造成递归调用。
      
              // 做法:比较更新前后的props是否相同,来决定是否重新渲染组件
              console.log('prevProps: ', prevProps, '--currentProps: ', this.props)
              if (prevProps !== this.props) {
                  this.setState({})
                  // 发送ajax请求
              }
              /* 
                  这里点击“打豆豆”按钮,会打印两次
                  结果:
                  prevProps:  {count: 0} --currentProps:  {count: 1}
                  prevProps:  {count: 1} --currentProps:  {count: 1}
                  原因:第一次是点击了按钮,父组件状态更新,子组件props发生变化,钩子函数顺序执行到componentDidUpdate(),第二次是因为走了if里面的语句,调用完setState后,重新render后又要立即去执行componentDidUpdate(),只不过此时没有改变父组件状态,所以前后props是一样的
              */
          }
      }
      
      export default App
      
  2. 卸载时(卸载阶段)

    • 执行时机:组件从页面中消失

    • 钩子函数componentWillUnmount()

      钩子函数触发时机作用
      componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)
      import React from 'react'
      
      /* 
          组件生命周期
      */
      // 卸载时(卸载阶段)
      class App extends React.Component {
          constructor(props) {
              super(props)
      
              // 初始化state
              this.state = {
                  count: 0
              }
          }
      
          handleClick = () => {
              this.setState({
                  count: this.state.count + 1
              })
          }
      
          render() {
              return (
                  <div>
                      {
                          this.state.count > 3
                              ? <p>豆豆被打西内了</p>
                              : <Counter count={this.state.count}></Counter>
                      }
                      <button id="btn" onClick={this.handleClick}>打豆豆</button>
                  </div>
              )
          }
      }
      
      class Counter extends React.Component {
      
          componentDidMount() {
              // 开启定时器
              this.timerId = setInterval(() => {
                  console.log('定时器正在执行~')
              }, 1000)
          }
      
          render() {
              return (
                  <h1 id="title">统计豆豆被打的次数:{this.props.count}</h1>
              )
          }
      
          componentWillUnmount() {
              console.warn('生命周期钩子函数:componentWillUnmount')
              // 清理定时器
              clearInterval(this.timerId)
          }
      }
      
      export default App
      
3. 不常用钩子函数

**介绍:**react库不断更新,新老不匹配,移除旧的不常用钩子函数,加入新的钩子函数

旧版的生命周期,创建和更新的componentWillxxx不用了

新版完整的生命周期,这两个新的getxxxx也不是很推荐使用

render-props和高阶组件
1. React组件复用
  1. 概述

    • 思考:如果两个组件中的部分功能相似或相同,该如何处理?
    • 处理方式:复用相似的功能(联想函数封装)
    • 复用什么?1.state 2.操作state的方法(组件状态逻辑)
    • 两种方式:1.render props模式 2.高阶组件(HOC:high order component)
    • 注意:这两种模式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
2. render props模式

(只是个名字,不一定就是使用 render)

context其实用的就是render props模式,而且是children属性

思路分析

  • 思路:将要复用的state和操作state的方法封装到一个组件中
  • 问题1:如何拿到该组件中复用的state?
  • 在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
  • 问题2:如何渲染任意的UI?
  • 使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)

使用步骤

  1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态;2.操作状态的方法)

  2. 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部

  3. 使用props.render()的返回值作为要渲染的内容

    import React from 'react'
    import PropTypes from 'prop-types'
    /* 
        render props模式
    */
    
    // 导入图片资源
    const catImgSrc = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.qiantucdn.com%2F58pic%2F27%2F51%2F73%2F30V58PICg4M_1024.jpg%21%2Ffw%2F780%2Fwatermark%2Furl%2FL3dhdGVybWFyay12MS4zLnBuZw%3D%3D%2Falign%2Fcenter%2Fcrop%2F0x1009a0a0&refer=http%3A%2F%2Fpic.qiantucdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634716498&t=8cf797a6f75a9a570b1c760dc682c9e5"
    
    // 创建Mouse组件
    class Mouse extends React.Component {
        // 鼠标位置state(状态)
        state = {
            x: 0,
            y: 0
        }
    
        // 鼠标移动处理程序(操作状态的方法)
        handleMouseMove = (e) => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }
    
        // 监听鼠标移动事件
        componentDidMount() {
            window.addEventListener('mousemove', this.handleMouseMove)
        }
    
        // 在组件卸载时移除事件绑定
        componentWillUnmount() {
            window.removeEventListener('mousemove', this.handleMouseMove)
        }
    
        render() {
            // return null
            // 没有渲染任何ui结构
            // return this.props.render(this.state)
    
            // 用children属性替换render
            return this.props.children(this.state)
        }
    }
    
    // 添加props校验
    Mouse.propTypes = {
        children: PropTypes.func.isRequired
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props)
            console.log(props)
        }
        render() {
            return (
                <div>
                    <h1>render props 模式</h1>
                    {/* 这里的mouse形参,拿到的就是上面Mouse组件的this.state */}
                    {/* <Mouse render={(mouse) => {
                        // 可以拿到状态,具体展示形式就可以在使用组件的地方自行定义
                        return <p>鼠标位置:{mouse.x},{mouse.y}</p>
                    }}></Mouse> */}
    
                    {/* 猫捉老鼠 */}
                    {/* <Mouse render={(mouse) => {
                        return <img src={catImgSrc} alt="猫" width={100} style={{
                            position: 'absolute',
                            top: mouse.y - 50,
                            left: mouse.x - 50
                        }}/>
                    }}></Mouse> */}
    
                    {/* children属性代替render */}
                    <Mouse>
                        {
                            (mouse) => {
                                return <p>children拿到鼠标位置:{mouse.x},{mouse.y}</p>
                            }
                        }
                    </Mouse>
                    {/* children属性代替render */}
                    {/* 猫捉老鼠 */}
                    <Mouse>
                        {
                            (mouse) => {
                                return <img src={catImgSrc} alt="猫" width={100} style={{
                                    position: 'absolute',
                                    top: mouse.y - 50,
                                    left: mouse.x - 50
                                }}/>
                            }
                        }
                    </Mouse>
                </div>
            )
        }
    }
    
    export default App
    

代码优化

  1. 推荐:给render props模式添加 props校验
  2. 应该在组件卸载时解除mousemove时间绑定
3. 高阶组件

概述

  • 目的:实现状态逻辑复用
  • 采用包装(装饰)模式,比如说:手机壳
  • 手机:获取保护功能
  • 手机壳:提供保护功能
  • 通过包装组件,增强组件功能

思路分析

  • 高阶组件(HOC,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件。
  • 高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给包装组件WrappedComponent

使用步骤

  1. 创建一个函数,名称约定以with开头
  2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  4. 在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中。

设置displayName

  • 使用高阶组件存在的问题: 得到的两个组件名称相同
  • 原因:默认情况下,React使用组件名称作为displayName
  • 解决方式:为高阶组件设置displayName便于调试时区分不同的组件
  • displayName的作用:用于设置调试信息(React Developer Tools信息)

传递props

  • 问题:props丢失
  • 原因:高阶组件没有往下传递props
  • 解决方式:渲染WrappedComponent时,将state和this.props一起传递给组件
import React from 'react'
/* 
    高阶组件
*/

// 导入图片资源
const catImgSrc = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.qiantucdn.com%2F58pic%2F27%2F51%2F73%2F30V58PICg4M_1024.jpg%21%2Ffw%2F780%2Fwatermark%2Furl%2FL3dhdGVybWFyay12MS4zLnBuZw%3D%3D%2Falign%2Fcenter%2Fcrop%2F0x1009a0a0&refer=http%3A%2F%2Fpic.qiantucdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634716498&t=8cf797a6f75a9a570b1c760dc682c9e5"


// 创建高阶组件函数
function withMouse(WrappedComponent) {
    // 该组件提供复用的状态逻辑
    class Mouse extends React.Component {
        // 鼠标状态
        state = {
            x: 0,
            y: 0
        }

        handleMouseMove = e => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }

        // 控制鼠标状态逻辑
        componentDidMount() {
            window.addEventListener('mousemove', this.handleMouseMove)
        }

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

        render() {
            console.log('Mouse: ', this.props)
            // 将state和this.props一起传递给组件
            return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
        }
    }

    // 设置displayName
    Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`

    return Mouse
}

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


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

// 猫捉老鼠组件
const Cat = props => (
    <img
        src={catImgSrc}
        alt="cat"
        width={100}
        style={{
            position: 'absolute',
            top: props.y - 50,
            left: props.x - 50
        }}
    />
)

// 获取增强后的组件
const MousePosition = withMouse(Position)
const MouseCat = withMouse(Cat)

class App extends React.Component {
    constructor(props) {
        super(props)
        console.log(props)
    }
    render() {
        return (
            <div>
                <h1>高阶组件</h1>
                {/* 渲染高阶组件 */}
                {/* 这里的a="1"相当于是传给了高阶函数里的Mouse组件 */}
                <MousePosition a="1"></MousePosition>
                <MouseCat></MouseCat>
            </div>
        )
    }
}

export default App
总结
  1. 组件通讯是构建React应用必不可少的一环
  2. props的灵活性让组件更加强大
  3. 状态提升是React组件的常用模式
  4. 组件生命周期有助于理解组件的运行过程
  5. 钩子函数让开发者可以在特定的时机执行某些功能
  6. render props模式和高阶组件都可以实现组件状态逻辑复用
  7. 组件极简模型:(state, props)=> UI
  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是从零开始学习React框架的学习路径、学习资料和资料链接: 1. HTML、CSS和JavaScript基础知识 在学习React之前,需要先掌握HTML、CSS和JavaScript的基础知识,包括HTML标签、CSS样式、JavaScript语法、DOM操作和事件处理等。可以通过以下资料进行学习: - MDN Web 文档:https://developer.mozilla.org/zh-CN/ - w3school 在线教程:https://www.w3school.com.cn/ - 菜鸟教程:https://www.runoob.com/ 2. React基础知识 学习React的基础知识,包括React的概念、组件、生命周期、事件处理等。可以通过以下资料进行学习: - React 官方文档:https://reactjs.org/docs/getting-started.html - React 小书:http://huziketang.mangojuice.top/books/react/ - React 实战教程:https://www.imooc.com/learn/1075 3. React进阶知识 学习React的进阶知识,包括React的路由、状态管理、性能优化等。可以通过以下资料进行学习: - React Router 官方文档:https://reactrouter.com/web/guides/quick-start - Redux 官方文档:https://redux.js.org/ - React性能优化实践指南:https://juejin.cn/post/6844904152138365453 4. 实战项目 最后,通过实战项目来练习React的应用。可以通过以下资料进行学习: - React 实战-打造画廊应用:https://www.imooc.com/learn/507 - React 实战-开发简易版微信:https://www.imooc.com/learn/651 - React 实战-开发音乐播放器:https://www.imooc.com/learn/868 希望以上资料对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值