react的自学日记

123 阅读18分钟

1、 react 的基本使用

1.1 脚手架创建react 项目

npx create-react-app 项目名称

运行

npm start

1.2 脚手架写程序

// 1、导入 react 
import React from 'react'
import ReactDOM from 'react-dom'

// React.createElement() 三个参数
// 参数1:htmlbi标签
// 参数2:标签的属性和属性值,以对象的形式出现
// 参数3:内容 或 节点

// 2、创建react 元素
const title = React.createElement('h1', null, 'hello react -- 脚手架')


// ReactDOM.render() 参数
// 参数1:创建的元素
// 参数2:需要渲染到哪个节点下面

// 3、渲染 react 元素
ReactDOM.render(title, document.getElementById('root'))

1.3 不同版本的写法

18.2 版本

// 1、导入 react 
import React from 'react'
import ReactDOM from 'react-dom/client'

// 2、创建 react 元素
const list = (
    <h1> hello world </h1>
)

// 获取根节点,渲染 数据
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(list);

17版本

// 1、导入 react 
import React from 'react'
import ReactDOM from 'react-dom'

// 2、创建 react 元素
const list = (
    <h1> hello world </h1>
)

// 3、渲染 react 元素
ReactDOM.render(list, document.querySelector('#root'))

2、JSX

2.1 JSX的基本使用

// 使用 JSX 创建react元素
const title = <h1>HEllo React</h1>

// 渲染 react 元素
ReactDOM.render(title, document.querySelector('#root'))

2.2 注意点

  1. React元素的属性名使用驼峰命名法

  2. 特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex 。

  3. 没有子节点的React元素可以用 /> 结束 。

  4. 推荐:使用小括号包裹 JSX ,从而避免 JS 中的自动插入分号陷阱。

2.3 JSX中使用 javascript 表达式

嵌入表达式 -- {} -- 可以写 js 语句

注意点:
    
     单大括号中可以使用任意的 JavaScript 表达式

     JSX 自身也是 JS 表达式

     注意:JS 中的对象是一个例外,一般只会出现在 style 属性中

     注意:不能在{}中出现语句(比如:if/for 等)

2.4 JSX的条件渲染

渲染的语句

// 控制渲染的语句
const isloading = false

// 条件渲染
const title = (
  <h1>
    条件渲染:
    { loadData() }
  </h1>
)

// 渲染 react 元素
ReactDOM.render(title, document.querySelector('#root'))

1、使用 if else 语句

// if else 语句
const loadData = () => {
  if(isloading) {
    return <div>loading...</div>
  }
  return <div>数据紧挨在你完毕,此处显示架子啊后的数据</div>
}

2、三元表达式

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

3、逻辑运算符

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

2.5 JSX 的样式处理

行内样式

<h1 className='title' style={ {color: 'red', backgroundColor: 'skyblue'} }>JSX样式处理</h1>

外部引入 css文件 -- 推介使用 className 方式给 JSX 添加样式

通过类名
<h1 className='title' style={ {color: 'red', backgroundColor: 'skyblue'} }>JSX样式处理</h1>

3 组件

3.1 组件的两种创建方式

3.1.1 使用函数创建组件

 函数组件:使用 JS 的函数(或箭头函数)创建的组件

 约定1:函数名称必须以大写字母开头

 约定2:函数组件必须有返回值,表示该组件的结构

 如果返回值为 null,表示不渲染任何内容

渲染函数组件

用函数名作为组件标签名  --- 单标签形式

root.render(<Hello />);
// 普通函数
function Hello() { 
  return (
    <div>这是饿哦的第一个函数组件</div>
  )
 }
// 箭头函数
const Hello = () => <div>这是呵呵哈哈哈</div>

3.1.2 使用类创建组件

 类组件:使用 ES6 的 class 创建的组件

 约定1:类名称也必须以大写字母开头

 约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性

 约定3:类组件必须提供 render() 方法

 约定4render() 方法必须有返回值,表示该组件的结构
// 类组件 -- 单标签渲染
class Hello extends React.Component {
  render() {
    return (
      <div>这是我第一个类组件</div>
    )
  }
}

3.1.3 抽离为独立 js 组件

1. 创建Hello.js

2. 在 Hello.js 中导入React
    // 导入 react 
    import React from 'react'
3. 创建组件(函数 或 类)
// 创建组件
class Hello extends React.Component {
  render() {
    return (
      <div>这是我第一个类组件 -- 抽离的</div>
    )
  }
}
4. 在 Hello.js 中导出该组件
// 导入组件
export default Hello
5. 在 index.js 中导入 Hello 组件
// 导入 Hello 组件
import Hello from './js/Hello';
6. 渲染组件
// 渲染组件 -- 18 版本的
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Hello />);

4 React 事件处理

4.1 事件绑定

语法:on+事件名称={事件处理程序},比如:onClick={() => {}}
注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
// 类组件
class App extends React.Component {
  // 事件处理程序
  handleClick() {
    console.log('单机事件');
  }

  render() {
    return (
      <button onClick={this.handleClick}>惦记我</button>
    )
  }
}
// 通过函数组件绑定事件
function App() {
  // 事件处理程序
  function handleClick() {
    console.log('函数组件中的事件绑定');
  }

  return(
    // 函数组件中是没有 this 的
    <button onClick={handleClick}>dianwo</button>
  )
}

4.2 事件对象 e

 可以通过事件处理程序的参数获取到事件对象

 React 中的事件对象叫做:合成事件(对象)

 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
/* React 事件对象 */

class App extends React.Component {

  handleClick(e) {
    // 阻止浏览器的默认行为
    e.preventDefault()
    console.log('a标签的单机事件触发了')
  }

  render() {
    return (
      <a href="https://baidu.com" onClick={this.handleClick}>百度</a>
    )
  }
}

4.3 有状态组件和无状态组件

 函数组件又叫做无状态组件,类组件又叫做有状态组件

 状态(state)即数据

 函数组件没有自己的状态,只负责数据展示(静)

 类组件有自己的状态,负责更新 UI,让页面“动” 起来

4.4 组件中的 state 和 setState

4.4.1 state的基本使用

 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用

 state 的值是对象,表示一个组件中可以有多个数据

 获取状态:this.state
/* state 的基本使用 */

class App extends React.Component {
  // constructor() {
  //   super()

  //   // 初始化state
  //   this.state = {
  //     count: 0
  //   }
  // }

  // 简化的语法
  state = {
    count: 10
  }
  //  获取状态:this.state
  render() {
    return (
      <div>
        <h1>计数器:{ this.state.count }</h1>
      </div>
    )
  }
}
 状态即数据

 状态是私有的,只能在组件内部使用

 通过 this.state 来获取状态

4.4.2 setState()修改状态

 状态是可变的

 语法:this.setState({ 要修改的数据 })

 注意:不要直接修改 state 中的值,这是错误的!!!

 setState() 作用:1. 修改 state    2. 更新UI

 思想:数据驱动视图
class App extends React.Component {
  // 简化的语法
  state = {
    count: 10
  }

  //  获取状态:this.state
  //  语法:this.setState({ 要修改的数据 })
  render() {
    return (
      <div>
        <h1>计数器:{ this.state.count }</h1>
        <button onClick={() => {this.setState({count: this.state.count + 1})}}>+1</button>
      </div>
    )
  }
}

4.5 从 JSX 中抽离事件处理程序

 原因:事件处理程序中 this 的值为 undefined

+ 希望:this 指向组件实例(render方法中的this即为组件实例)
class App extends React.Component {
  // constructor() {
  //   super()

  //   // 初始化state
  //   this.state = {
  //     count: 0
  //   }
  // }

  // 简化的语法
  state = {
    count: 10
  }

  // 抽离事件处理程序
  onIncrement() {

    //console.log('事件处理函数中的 this 指向', this); // undefined

    this.setState({
      count: this.state.count + 1
    })
  }
  //  获取状态:this.state
  render() {
    return (
      <div>
        <h1>计数器:{ this.state.count }</h1>
        <button onClick={this.onIncrement}>+1</button>
        {/* <button onClick={() => {this.setState({count: this.state.count + 1})}}>+1</button> */}
      </div>
    )
  }
}

4.5.1 解决抽离事件处理程序中的 this 指向

1、箭头函数

class App extends React.Component {
  // 简化的语法
  state = {
    count: 10
  }
  // 事件处理程序
  onIncrement() {
    this.setState({
      count: this.state.count + 1
    })
  }

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

2、Function.prototype.bind()

class App extends React.Component {
  constructor() {
    super()
    
    // 把 onIncrement 的 this 指向 这个类的 this 
    this.onIncrement = this.onIncrement.bind(this)
  }

  // 简化的语法
  state = {
    count: 10
  }
  // 事件处理程序
  onIncrement() {
    this.setState({
      count: this.state.count + 1
    })
  }

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

3、class 的实例方法 --- 推介使用 利用箭头函数形式化的 class 实例方法

class App extends React.Component {
  // 简化的语法
  state = {
    count: 10
  }

  // 事件处理程序 -- class 箭头函数的实例对象
  onIncrement = () => {
    console.log('事件处理函数中的 this 指向', this); // undefined
    this.setState({
      count: this.state.count + 1
    })
  }

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

5 表单处理

5.1 受控组件和非受控组件(DOM方式)

5.1.1 受控组件

class App extends React.Component {
  // 简化的语法
  state = {
    txt: ''
  }

  handleChange = (e) => {
    this.setState({
      txt: e.target.value
    })
  }

  render() {
    return (
      <div>
        <input type='text' value={this.state.txt} onChange={this.handleChange}></input>
      </div>
    )
  }
}

5.1.1.1 实例 -- 文本框 - 富文本框 - 下拉框 - 复选框(checked)

class App extends React.Component {
  // 简化的语法
  state = {
    txt: '',
    content: '',
    city: 'bj',
    isChecked: false
  }

  handleChange = (e) => {

    // console.log(e.target.value);

    // 利用事件对象
    this.setState({
      txt: e.target.value
    })
  }

  handleContent = (e) => {
    this.setState({
      content: e.target.value
    })
  }

  handleCity = e => {
    this.setState({
      city: e.target.value
    })
  }

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

  render() {
    return (
      <div>
        {/* 文本框 */}
        文本框
        <input type='text' value={this.state.txt} onChange={this.handleChange}></input>

        <br/>
        富文本框
        {/* 富文本框 */}
        <textarea value={this.state.content} onChange={this.handleContent}></textarea>
        <br/>
        
        下拉框
        <select value={this.state.city} onChange={this.handleCity}>
          <option value='sh'>上海</option>
          <option value='bj'>北京</option>
          <option value='gz'>广州</option>
        </select>
        <br />
        
        {/* 复选框 */}
        复选框
        <input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked}></input>
      </div>

    )
  }
}

优化多表单元素

  handleForm = e => {
    // 获取当前的 DOM 对象
    const target = e.target

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

    // 获取name
    const name = target.name
    
    this.setState({
    // 根据 name 设置对应的 state
    // [name] 类似于 {} 引用属性
    [name]: value
    })
  }

5.1.2 非受控组件

/* 非受控组件 */
class App extends React.Component {
  constructor() {
    super()

    // 1. 调用 React.createRef() 方法创建一个 ref 对象
    this.txtRef = React.createRef()
  }

  // 获取文本框的值
  getTxt = e => {
    // 3. 通过 ref 对象获取到文本框的值
    console.log('文本框的值:' ,this.txtRef.current.value);
  }

  render() {
    return(
      <div>
        {/* // 2. 将创建好的 ref 对象添加到文本框中 */}
        <input type='text' ref={this.txtRef}></input>
        <button onClick={this.getTxt}>获取文本框的值</button>
      </div>
    )
  }
}

6 组件高级

6.1 组件的 prop

 1、组件是封闭的,要接收外部数据应该通过 props 来实现
 2、props的作用:接收传递给组件的数据
 3、传递数据:给组件标签添加属性
 4、接收数据:函数组件通过参数props接收数据,类组件通过 this.props 接收数据
/* 接收数据 */
/* 函数组件 */
const App = (props) => {
  // props 是一个对象
  console.log(props);
  return (
    <div>
      <h1>props: {props.name}</h1>
    </div>
  )
}

/* 类组件 */
class App extends React.Component {
  render() {
    console.log(this.props);
    return (
      <div>
       <h1>props: {this.props.name}</h1>
      </div>
    )
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'))

// 传递数据
root.render(<App name="jack" age={18} />)

特点

1. 可以给组件传递任意类型的数据
2. props 是只读的对象,只能读取属性的值,无法修改对象
3. 注意:使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props!
/* 类组件 */
class App extends React.Component {
  // 使用 constructoe 函数时 没有传递 props ,this.props 是 undefined
  constructor(props) {
    super(props)
    console.log(this.props);
  }
  
  render() {
     // render函数中可以获取到 this.props 对象
    console.log(this.props);
    return (
      <div>
       <h1>props: {this.props.name}</h1>
      </div>
    )
  }
}

6.2 组件通讯

6.2.1 父组件 --> 子组件

1、父组件提供要传递的 state 数据
2、子组件标签提添加属性,值为 state 中的数据
3、子组件通过 props 接收数据
/* 父组件 */
class Parent extends React.Component {
  // 1、父组件提供要传递的 state 数据
  state = {
    userName: '蓝'
  }
  render() {
    return (
      <div>
        {/* 2、子组件标签提添加属性,值为 state 中的数据 */}
        传递数据给子组件:<Child name={this.state.userName}/>
      </div>
    )
  }
}

/* 子组件 */
class Child extends React.Component {
  // 3、子组件通过 props 接收数据
  render() {
    return (
      <div>子组件接收到的数据:{this.props.name}</div>
    )
  }
}

6.2.2 子组件 --> 父组件

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

1. 父组件提供一个回调函数(用于接收数据)

2. 将该函数作为属性的值,传递给子组件
class Parent extends React.Component {

  state = {
    parentMsg: ''
  }

  // 提供回调函数,用来接收数据
  getChildMsg = data => {
    console.log('接收到子组件传递过来的数据:', data);

    this.setState({
      parentMsg: data
    })
  }

  render() {
    return (
      <div>
        父组件: {this.state.parentMsg}
        <Child getMsg={this.getChildMsg}/>
      </div>
    )
  }
}
3. 子组件通过 props 调用回调函数
4. 将子组件的数据作为参数传递给回调函数
class Child extends React.Component {
  state = {
    msg: '哈哈哈'
  }

  handleClick = () => {
    
    // 子组件调用父组件中传递过来的回调函数
    this.props.getMsg(this.state.msg)
  }

  render() {
    return (
      <div>
        子组件:
        <button onClick={this.handleClick}>点击我,传递数据</button>
      </div>
    )
  }
}

6.2.3 兄弟组件

1、将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
2、思想:状态提升
3、公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法
4、要通讯的子组件只需通过 props 接收状态或操作状态的方法
class Counter extends React.Component {

  // 提供共享状态
  state = {
    count: 0
  }

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

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

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

const Child2 = (props) => {
  return <button onClick={() => props.onIncrement()}>+1</button>
}

6.2.4 Context -- 跨组件传递数据

1. 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
2. 使用 Provider 组件作为父节点。
3. 设置 value 属性,表示要传递的数据。
4. 调用 Consumer 组件接收数据。
// 1、创建 context 得到两个组件
const {Provider, Consumer} = React.createContext()
// 2. 使用 Provider 组件作为父节点。
class App extends React.Component {
  render() {
    return (
      <Provider value="pink">
        <div>
          <Node />
        </div>
      </Provider>

    )
  }
}
        // 3. 设置 value 属性,表示要传递的数据。
      <Provider value="pink">
        <div>
          <Node />
        </div>
      </Provider>

// 4. 调用 Consumer 组件接收数据
const Child = props => {
  return (
    <div>
      <Consumer>
        {
          data => <span>我是子节点 --- {data}</span>
        }
      </Consumer>
    </div>
  )
}

6.3 props 深入

6.3.1 children 属性

1、 children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
2、 children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
class App extends React.Component {
  
  render() {
    console.log(this.props);
    return (
      <div>
        <h1>组件标签的子节</h1>
        {this.props.children}
      </div>
    )
  }
} 
// 文本节点
root.render(<App>我是子节点</App>)

// 标签节点
root.render(<App><p>我是一个p标签</p></App>)
// Text 组件
const Text = () => <button>我是button 标签</button>

// 组件节点
root.render(<App><Text /></App>)
class App extends React.Component {
  
  render() {
    console.log(this.props);
    this.props.children()
    return (
      <div>
        <h1>组件标签的子节</h1>
      </div>
    )
  }
} 

// 函数节点
root.render(<App>{() => console.log('这是一个函数子节点')}</App>)

6.3.2 props 校验

使用步骤

1. 安装包 prop-types (yarn add prop-types / npm i props-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) => <li key={index}>{item}</li>)

  return <ul>{lis}</ul>
}

// 添加 props 校验
App.propTypes ={
  colors: PropTypes.array
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App colors={['red','blue','sky']} />)

约束规则

1. 常见类型:array、bool、func、number、object、string
2. React元素类型:element
3. 必填项:isRequired
4. 特定结构的对象:shape({ })
import PropTypes from 'prop-types'

const App = props => {
  return (
    <div>
      <h1>props校验: </h1>
    </div>
  )
}

// 添加 props 校验
// 属性 a 的类型      数值(number)
// 属性 fn 的类型     函数(func) 并且为必填项
// 属性 tag 类型      React元素 (element)
// 属性 fiflter 类型  对象({area: '上海',price: 1999})

App.propTypes = {
  a: PropTypes.number,
  fn: PropTypes.func.isRequired,
  tag: PropTypes.element,
  filter: PropTypes.shape({
    area: PropTypes.string,
    price: PropTypes.number
  })
}

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

props 的默认值

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

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

const root = ReactDOM.createRoot(document.getElementById('root'))

// 没有传值的话,就以约束的默认值显示
root.render(<App pageSize={20}/>)

7 组件的生命周期

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

1、创建时(挂载阶段)

1constructor() -- 创建组件时,最先执行 -- 1. 初始化state 2. 为事件处理程序绑定this
2render()      -- 每次组件渲染都会触发 -- 渲染UI(注意:不能调用setState())
3componentDidMount() -- 组件挂载(完成DOM渲染)后 -- 1. 发送网络请求 2. DOM操作
class App extends React.Component {

  constructor(props) {
    super(props)
    console.warn('生命周期钩子函数: constructor');

    // 初始化 state 
    this.state = {
      count: 0
    }
  }

  // 进行 DOM 操作
  // 发送 ajax 请求,获取远程数据
  componentDidMount() {
    console.warn('生命周期钩子函数: componentDidMount');
  }

  // 打豆豆
  handleClick = () => {
    // this.setState({
    //   count: this.state.count + 1
    // })

    // 强制更新
    this.forceUpdate()
  }

  render() {
    console.warn('生命周期钩子函数: render');
    return (
      <div>
        <Counter count={this.state.count} />
        <button id="btn" onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}

2. 更新时(更新阶段)

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

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

 执行顺序: render() -- componentDidUpdate()
render -- 每次组件渲染都会触发 -- 渲染UI(与 挂在阶段 是同一个render)

componentDidUpdate -- 组件更新(完成DOM渲染)后 -- 1 发送网络请求

                                                  2 DOM操作

                                                  注意:如果要setState() 必须放在一个if条件中

render()

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

    // 强制更新
    this.forceUpdate()
  }

componentDidUpdate()

  // 注意:如果要调用 setState() 更新状态,必须要放在一个 if 条件中
  // 因为: 如果直接调用 setState() 更新状态,也会导致递归更新
  componentDidUpdate(provProps) {
    console.warn('生命周期钩子函数: componentDidUpdate');

    // 比较更新前后的 props 是否相同,来决定是否重新渲染组件
    console.log('上一次的props:', provProps, '当前的props: ', this.props);
    if (provProps.count !== this.props.count) {
      this.setState()
      // 发送 ajax 请求
    }

    // 获取 DOM 
    // const btn = document.getElementById('btn')
    // console.log(btn.innerHTML);
  }

3. 卸载时(卸载阶段)

componentWillUnmount() -- 组件卸载(从页面中消失) -- 执行清理工作(比如:清理定时器等)

8 render-props和高阶组件(HOC)

8.1 render props 模式

使用步骤

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

父组件提供一个回调函数--通过对子组件添加属性的回调函数(父组件提供回调函数) -- 子组件调用这个回调函数传递参数
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)
  }

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

class App extends React.Component {
  render() {
    return(
      <div>
        <h1>render props 模式</h1>
        <Mouse render={(mouse) => {
          return <p>鼠标位置: x:{mouse.x} , y:{mouse.y}</p>
        }}/>
        
        {/* children 模式 */}
        <Mouse>{ mouse => { return <p>鼠标位置: x:{mouse.x} , y:{mouse.y}</p> } }</Mouse>
      </div>
    )
  }
}

代码优化

  1. 推荐:给 render props 模式添加 props校验
  // 组件的卸载周期函数
  // 推介:在组件卸载时移除事件绑定
  componentWillUnmount() {
    window.removeEventListener('mousemove', this.handleMouseMove)
  }
  1. 应该在组件卸载时解除 mousemove 事件绑定
import PropTypes from 'prop-types'

// 代码优化 -- 添加 props 校验
Mouse.propTypes = {
  children: PropTypes.func.isRequired
}

8.2 高阶组件(HOC)

使用步骤

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

/* 创建高阶组件 */
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() {
      return <WrappedComponent {...this.state}></WrappedComponent>
    }
  }

  return Mouse
 }

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

}

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

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

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

9 React 原理揭秘

9.1 setState() 的说明

1setState() 是异步更新数据的
2、注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
3、可以多次调用 setState() ,只会触发一次重新渲染

推荐语法

 推荐:使用 setState((state, props) => {}) 语法

 参数state:表示最新的state

 参数props:表示最新的props
import React from "react"

import ReactDOM from 'react-dom/client'

class App extends React.Component {

  state = {
    count: 0
  }

  // 推介的更新语法
  handleChange = () => {
    this.setState((state,props) => {
      return {
        count: state.count + 1
      }
    },
    // 第二个参数:状态更新后并且重新渲染后,立即执行
     () => {
      console.log('状态更新完成:', this.state.count);
    })
  }

  render() {
    return (
      <div>
        <p>数值: {this.state.count}</p>
        <button onClick={this.handleChange}>+1</button>
      </div>
    )
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

第二个参数

 场景:在状态更新(页面完成重新渲染)后立即执行某个操作

 语法: setState(updater[, callback])

9.2 JSX 语法的转化过程

 JSX 仅仅是 createElement() 方法的语法糖(简化语法)

 JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法

 React 元素:是一个对象,用来描述你希望在屏幕上看到的内容

JSX 语法 -- > createElement() -- > React 元素

9.3 组件性能优化

减轻 state

 减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)

 注意:不用做渲染的数据不要放在 state 中,比如定时器 id等

 对于这种需要在多个方法中用到的数据,应该放在 this
class Hello extends Component {

componentDidMount() {

// timerId存储到this中,而不是state中

this.timerId = setInterval(() => {}, 2000)

}

componentWillUnmount() {

clearInterval(this.timerId)

}

render() { … }

}

避免不必要的重新渲染

 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰

 问题:子组件没有任何变化时也会重新渲染

 如何避免不必要的重新渲染呢?

 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)

 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染

 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate  render)
  // 钩子函数
  shouldComponentUpdate(nextProps,nextState) {
    // 返回false, 阻止组件重新渲染
    // return false

    // 最新的状态
    console.log('最新的state: ', nextState);

    // 更新前的状态
    console.log('this.state: ',this.state);

    return true
  }

避免不必要的重新渲染

class Hello extends Component {

shouldComponentUpdate(nextProps, nextState) {

return nextState.number !== this.state.number

}

render() {…}

}

子组件的避免不必要的更新

class Hello extends Component {

shouldComponentUpdate(nextProps, nextState) {

return nextProps.number !== this.props.number

}

render() {…}

}

9.4 纯组件

 纯组件:PureComponent 与 React.Component 功能相似

 区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较

 原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {

render() {

return (

<div>纯组件</div>

)

}

}
 说明:纯组件内部的对比是 shallow compare(浅层对比)

 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)



 说明:纯组件内部的对比是 shallow compare(浅层对比)

 对于引用类型来说:只比较对象的引用(地址)是否相同

注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!(示例)
state = { obj: { number: 0 } }

// 错误做法

state.obj.number = 2

setState({ obj: state.obj })

// PureComponent内部比较:

最新的state.obj === 上一次的state.obj // true,不重新渲染组件




// 正确!创建新数据

const newObj = {...state.obj, number: 2}

setState({ obj: newObj })

// 正确!创建新数据

// 不要用数组的push / unshift 等直接修改当前数组的的方法

// 而应该用 concat 或 slice 等这些返回新数组的方法

this.setState({

list: [...this.state.list, {新数据}]

})

9.5 虚拟 DOM 和 Diff 算法

 React 更新视图的思想是:只要 state 变化就重新渲染视图

 特点:思路非常清晰

 问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是

 理想状态:部分更新,只更新变化的地方。

 问题:React 是如何做到部分更新的? 虚拟 DOM 配合 Diff 算法

虚拟 DOM:本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)。

执行过程

1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。

2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。

3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。

4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。

5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。

10 React 路由

10.1 路由的基本使用