react超详细讲解-看这一篇就够了(2)

285 阅读24分钟

5-1. 组件拆分

在这里插入图片描述

5-2. 静态布局

5-2-1. TodoList组件布局

  1. 创建对应组件的目录及文件
  2. 将整体html结构和整体的css,都拷贝到TodoList组件中,完成页面的正常显示
  3. 修改class ==> className

5-2-2. 将TodoList组件中的结构和样式拆分到各个组件

5-3. 首屏数据渲染

渲染列表:必须是数组。列表中的数据信息比较多,所以数组的每一项都得是一个对象

根据静态页面效果,分析数据结构如下:

todos = [
    {
        id:1
        title:'吃饭'
        isDone:true
    },
    {
        id:2,
        title:'睡觉',
        isDone:false
    }
]

状态提升

5-4. Footer-总条数-已完成数

let {todos} = this.props
let total = todos.length;
let doneNum = todos.reduce((pre, cur)=>pre + cur.isDone,0)

5-5. 添加代办事项

  1. 语言描述要做什么【干什么->效果】:

    在头部文本框输入内容,按回车后,可以渲染到列表中

  2. 拆分步骤:

    1. 文本框输入内容:
    2. 按回车
    3. 渲染到列表
  3. 根据步骤罗列技术点

    1. 获取文本框输入内容
      1. 非受控组件 ref:
      2. e.target:当时间源是文本框的时候,可以有限考虑使用
      3. 受控组件
    2. 按回车:
      1. 绑定键盘事件: onKeyUp、onKeyDown
      2. 判断按的是不是回车: keycode === 13 或 code == 'Enter'
    3. 输入的内容渲染到列表:
      1. 子传父: 数据在哪里,参数数据的方法就要定义在哪里
        1. 父组件中定义方法
        2. 方法通过属性传递给子组件
        3. 子组件调用该方法,传递数据
      2. 创建一个新的对象 加入到 todos数组中,重置状态即可
  • Header.jsx

    1. 绑定keyup时间
    2. 获取文本框输入内容 e.target.value
    3. 调用父组件方法 传递title
    4. 清空文本框
import React, { Component } from 'react'
import './index.css'
export default class Header extends Component {
    // 通过ref获取
    // titleRef = React.createRef()

    // 通过受控组件获取
    // state = {
    //     title: ''
    // }
    // changeHandler(e){
    //     this.setState({
    //         title:e.target.value
    //     })
    // }

    keyupHandler(e) {
        if (e.code === 'Enter') {
            // 通过ref获取:
            // console.log('ref: ', this.titleRef.current.value.trim())
            // 受控组件获取
            // console.log('state: ', this.state.title)


            // 1. 获取文本框输入的内容
            let title = e.target.value.trim()
            console.log(title)
            // 2. 调用父组件中的 addTodo方法,传递title
            this.props.addTodo(title)
            // 3. 清空文本框
            e.target.value = ''
        }
    }
    render() {
        return (
            <div className="todo-header">
                <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} />
                {/* <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} ref={this.titleRef} /> */}
                {/* <input type="text" placeholder="请输入你的任务名称,按回车键确认"
                    value={this.state.title}
                    onKeyUp={this.keyupHandler.bind(this)}
                    onChange={this.changeHandler.bind(this)}
                /> */}
            </div>
        )
    }
}

  • TodoList.jsx
  1. 定义addTodo方法
    1. 获取header传递过来的title
    2. 拼接 todo对象
      1. 生成id : nanoid
        1. 安装:npm i nanoid
        2. 导入: import {nanoid} from 'nanoid'
        3. 生成: nanoid()
    3. 添加并重置状态todos
  2. 传递给Header组件
// 添加todo
addTodo(title){
    // 1. 获取子组件header中的输入内容title
    console.log('TodoList : ',title)
    // 2. 拼一个新的todo对象
    const todo = {
        id:nanoid(),
        title,
        isDone:false
    }
    // 3. 加入数组,并执行setState触发更新
    this.setState({
        todos:[todo, ...this.state.todos]
    })
}

5-6. 删除todo

  1. 描述[干了什么-》效果]:点击删除按钮,删除当前事项
  2. 步骤:
    1. 点击按钮
    2. 删除该条记录
  3. 技术:
    1. 点击按钮
      1. 绑定单击事件: onClick
    2. 删除该条记录
      1. 获取当前记录id
      2. 将id传递到 todos数据所在组件[TodoList组件]
      3. 在TodoList组件中定义 删除方法,根据id删除
      4. 重置状态数据,刷新列表

注意:

  1. 方法定义在TodoList组件中,因为数据在哪儿,操作数据的方法就在哪儿
  2. 方法要从爷爷组件【TodoList】,传递给孙子组件Item,List做为桥梁
  • TodoList.jsx

    定义 deleteById方法

    1. 接收到item组件传递的 id
    2. 根据id 过滤todos数据
    3. 重置状态todos

    deleteById传递给 List组件

// 根据id删除todo
deleteById(id){
    console.log('TodoList: id', id)
    // 根据id删除,其实就是保留下,id不相等的记录
    let newTodos = this.state.todos.filter(item=>item.id !== id)
    // 重置状态数据
    this.setState({
        todos:newTodos
    })
}
  • List.jsx

接收TodoList传递的 deleteById 方法,直接传递给Item组件

render() {
    let {todos,deleteById} = this.props
    return (
        <ul className="todo-main">
            {todos.map(todo=><Item todo={todo} deleteById={deleteById} key={todo.id}/>)}
        </ul>
    )
}
  • Item.jsx
  1. 接收deleteById方法

  2. 调用并传递id参数

render() {
    let {todo,deleteById} = this.props
    return (
        <li>
            <label>
                <input type="checkbox" checked={todo.isDone} onChange={()=>{}}/>
                <span className={todo.isDone ? 'done' : ''}>{todo.title}</span>
            </label>
            <button className="btn btn-danger" onClick={()=>deleteById(todo.id)}>删除</button>
        </li>
    )
}

5-7. 每条复选框完成已完成切换

  1. 描述功能:点击复选框,进行完成和未完成切换
  2. 步骤:
    1. 点击复选框
    2. 切换该条记录的完成未完成状态
  3. 技术点:
    1. 点击复选框:
      1. 定义onChange事件
    2. 切换该条记录的完成未完成状态
      1. 获取该条记录id
      2. 将变更切换的方法[checkedOne]定义在 TodoList组件身上
      3. 将该方法传递给item,执行,并传递id,还要传isDone最新的值
      4. 在方法 checkedOne 中
        1. 获取id,获取最新的checked值
        2. 遍历修改数组中对应id 的 isDone值为checked
        3. 重置状态todos
  • item.jsx

{/* 
方式一
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo.id,!todo.isDone)}/> 
*/}

// 方式二:
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>
        
  • TodoList.jsx
// checkedOne切换 id条记录的 isDone属性
// 方式一:
// checkedOne(id, isDone){
//     console.log('TodoList id: ', id)// 1
//     console.log('isDone: ', isDone)// true
//     let newTodos = this.state.todos.map(todo=>{
//         // 如果是当前的todo,那么修改isDone
//         if(todo.id === id){
//             todo.isDone = isDone
//         }
//         return todo
//     })
//     this.setState({
//         todos: newTodos
//     })
// }

// 方式二:
checkedOne(todo){
    todo.isDone = !todo.isDone
    // console.log(this.state.todos)
    this.setState({
        todos:[...this.state.todos]
    })
}
  • 方式二原理图

在这里插入图片描述

5-8. checkAll

全选反选功能实现

  1. 获取最新的checked 值
  2. todos数组中每一个todoisDone属性修改成 checked
  • TodoList.jsx
checkedAll(isDone){
    console.log('isDone: ', isDone)
    // 使用isDone 去修改todos数组中每一个todo 的isDone属性即可
    this.setState({
        todos:this.state.todos.map(todo=>{
            todo.isDone = isDone
            return todo
        })
    })
    // let newTodos = this.state.todos.map(todo=>{
    //     todo.isDone = isDone
    //     return todo
    // })
    // this.setState({
    //     todos:newTodos
    // })
}

  • Item.jsx
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>

5-9. 删除已完成

过滤掉 isDone 是true的值,保留下 isDone是false的数据

  • TodoList.jsx
deleteDone(){
    this.setState({
        todos:this.state.todos.filter(todo=>!todo.isDone)
    })
}
  • Item.jsx
<button className="btn btn-danger" onClick={deleteDone}>清除已完成任务</button>

5-10. 将todos数据做本地化存储

localStorage

  1. 存储: localStorage.setItem(key, value)
  2. 读取: localStorage.getItem(key)
  3. 移除:localStorage.removeItem(key)
  4. 清空:localStorage.clear()

需求:

将todos数组存储在 localStorage中:

  1. 存储: JSON.stringify(数组)

  2. 读取:JSON.parse(JSON格式字符串)

6. 高阶组件

HOC:H higher O Order C component

高阶函数:函数的参数是函数或者函数的返回值是函数

高阶组件:组件的参数是组件或组件的返回值是组件

高阶组件作用:可以实现类组件代码的复用

6-1. 高阶函数

/**
         *  函数的参数是函数,或函数的返回值是函数,就是高阶函数
         *  
         *  const app = express()
         *  
         *  // 中间件
         *  app.use((req,res,next)=>{
         * 
         *  })
         *  
         */

function sum(a,b,c){
    return a + b + c;
}

console.log(sum(1,2,3))

// 高阶函数sum
// 柯里化函数:参数分步骤传递
// 为什么分开传?
// 
function sum2(a){
    return function (b){
        return function (c){
            return a + b + c;
        }
    }
}

let res = sum2(1)(2)(3)
console.log('res: ', res)


/**          1000    5000   3000         50个亿   3000万
         *  完事具备:资金 、 场地、 人员、 关系、 资源 、 粉丝、
         *  啥也没有:1000、  5000   3000
         * 
         */

6-2. 高阶组件

// 1.0 
// function WithApp(Com){
//     return Com
// }

import {Component} from 'react'
// 2.0
// function WithApp(Com){
//     return class  extends Component{
//         render(){
//             return <Com/>
//         }
//     }
// }

// 3.0 属性代理
// function WithApp(Com){
//     return class  extends Component{
//         render(){
//             return <Com username="atguigu" age="10"/>
//         }
//     }
// }

// 4.0 属性代理
// function WithApp(Com,props){
//     return class  extends Component{
//         render(){
//             return <Com {...props}/>
//         }
//     }
// }

// 5.0 参数分阶段传递
// function WithApp(props){
//     return function (Com){
//         return class  extends Component{
//             render(){
//                 return <Com {...props}/>
//             }
//         }
//     }
// }

// 6. 0 状态数据和props一起传递给子组件Com
// function WithApp(props){
//     return function (Com){
//         return class  extends Component{
//             state = {
//                 ...props,
//                 money:1000
//             }
//             render(){
//                 return <Com {...this.state}/>
//             }
//         }
//     }
// }

// 7.0
function WithApp(mapStateToProps){
    return function (Com){
        return class  extends Component{
            state = {
                ...mapStateToProps(),
                money:1000
            }
            render(){
                return <Com {...this.state}/>
            }
        }
    }
}

export default WithApp

6-3. 属性代理实现代码复用

高阶组件的作用:就是为了实现类组件的逻辑的复用

  • hoc/WithMove.jsx
import { Component } from "react";
/**
 * 
 * @param {*} WC  W Wrapper  C component
 */
export default function WithMove(WC) {
    return class extends Component {
        state = {
            x: 120,
            y: 60
        }
        moveHandler(e) {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }
        componentDidMount() {
            window.addEventListener('mousemove', this.moveHandler.bind(this))
        }
        componentWillUnmount() {
            window.removeEventListener('mousemove', this.moveHandler.bind(this))
        }
        render(){
            return <WC {...this.state}/>
        }
    }
}
  • components/TianE.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'

class TianE extends Component {
    
    render() {
        let {x,y} = this.props
        return (
            <div style={{width:100,height:100,border:'1px solid red',position:'absolute',left:x,top:y}}>尚硅谷的女老师们!!!</div>
        )
    }
}

export default WithMove(TianE)

  • components/HaMa.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'

class HaMa extends Component {
    
    render() {
        let {x,y} = this.props
        x += 110
        y += 110
        return (
            <div  style={{width:100,height:100,border:'1px solid green',position:'absolute',left:x,top:y}}>
                晶哥!!!
            </div>
        )
    }
}
export default WithMove(HaMa)
  • App.jsx
import React, { Component } from 'react'
import HaMa from './components/HaMa'
import TianE from './components/TianE'

export default class App extends Component {
    render() {
        return (
            <div>
                <TianE/>
                <HaMa></HaMa>
            </div>
        )
    }
}

7. Hook

7-1. useState

给函数组件创建状态数据的

语法:

let [状态, 设置状态函数] = useState(初始值)

7-1-1. setXxx 函数的两种用法

  1. setXxx(要赋的值):

  2. setXxx(回调函数):

    1. 回调函数的参数: 最新的变化后的状态数据
    2. 回调函数的返回值:要设置的最新的值

    应用:当使用方式一设置值不正确,发现无法获取到最新的数据时,可能是由于闭包导致的。所以,要考虑使用方式二

import React, {useEffect, useState} from 'react'

export default function App() {
    let [msg, setMsg] = useState('atguigu')

    useEffect(()=>{
        // componentDidMount
        setTimeout(()=>{
            // 方式一:
            // msg 只能取到初始值 atguigu, 并不能取到最新变化后的值
            // setMsg(msg + '_')

            // 方式二:
            // 参数是一个回调函数,回调函数的参数,是最新的变化后的状态数据
            // 回调函数的返回值,是设置的最新的状态数据的值
            setMsg((msg)=>{
                return msg + '_'
            })
        },2000)
    },[])
    return (
        <div>
            <h3>msg: {msg}</h3>

            <p><button onClick={()=>{
                setMsg(msg + '+')
            }}>msg + </button></p>
        </div>
    )
}

7-2. useEffect

作用:模拟生命周期函数

  1. componentDidMount
  2. componentDidUpdate
  3. componentWillUnmount

注意:

  1. useEffect 可以调用多次

     // 可以同时调用多次
    useEffect(()=>{
        console.log('didMount2')
    },[])
    // 可以同时调用多次
    useEffect(()=>{
        console.log('didMount3')
    },[money])
    
用法一:只有第一个参数回调函数,没有第二个参数

useEffect(()=>{ //  componentDidMount + componentDidUpdate[所有state的变化都触发 + 所有的props变化也触发]console.log('用法一:run')

})
// 用法二: 第二个参数是一个空数组,单独模拟componentDidMount
// useEffect(()=>{ // componentDidMount
//   console.log('用法二:')
// },[])


// 用法三: 第二个参数是数组,数组中指定监听的元素
// useEffect(()=>{ // componentDidMount + componentDidUpdate[监听谁 谁变化]
//   console.log('用法三:')
// },[money,count])

// 用法四:同时模拟 componentDidMount- 挂载 和 componentWillUnmount-卸载
useEffect(()=>{
    // componentDidMount()
    console.log('挂载后触发')
    return ()=>{
        // 这里模拟的是 componentWillUnmount
        console.log('componentWillUnmount - 模拟组件销毁是触发')
    }
},[])

7-3. useRef

作用:创建ref对象,在函数组件中绑定获取真实dom元素

用法:

  1. 引入: import {useRef} from 'react'

  2. 创建:

    const inputRef = useRef()

  3. 绑定:

    <input type='text' ref={inputRef} />

  4. 获取:

    inputRef.current

7-3-1. 受控组件

import React, { useRef, useState } from 'react'

export default function App() {
    // 定义状态数据
    let [username, setUsername] = useState('atguigu')
    let [pwd, setPwd] = useState('123')
    let [sex, setSex] = useState("1")
    let [city, setCity] = useState('1')
    let [hobby, setHobby] = useState(["1","3"])

   

    const submitHanlder = (e)=>{
        e.preventDefault()
        
    }

    // function usernameChange(e){
    //     console.log(e.target.value)
    //     setUsername(e.target.value)
    // }
    // function pwdChange(e){
    //     setPwd(e.target.value)
    // }

    function changeHandler(fn){
        return (e)=>{
            let value = e.target.value
            if(e.target.type === 'checkbox'){
                let index = hobby.findIndex(item=>item === e.target.value)
                value = [...hobby]
                if(index === -1)value.push(e.target.value)
                else value.splice(index, 1)
            }
            fn(value)
        }
    }

    return (
        <div>
            <div>
                <form action="" onSubmit={submitHanlder}>
                    <p>username: <input type="text" name="" value={username} onChange={changeHandler(setUsername)}/></p>
                    <p>pwd: <input type="text" name="" value={pwd} onChange={changeHandler(setPwd)}/></p>
                    <p>性别:
                        <input type="radio" name="sex"  value="1" checked={sex==="1"} onChange={changeHandler(setSex)}/><input type="radio" name="sex"  value="0" checked={sex==="0"} onChange={changeHandler(setSex)}/></p>
                    <p>city: 
                        <select name="" value={city} onChange={changeHandler(setCity)}>
                            <option value="1">北京</option>
                            <option value="2">上海</option>
                            <option value="3">深圳</option>
                        </select>
                    </p>
                    <p>爱好:
                        <input type="checkbox" name="" value="1" checked={hobby.includes("1")} onChange={changeHandler(setHobby)}/> 台球
                        <input type="checkbox" name="" value="2" checked={hobby.includes("2")} onChange={changeHandler(setHobby)}/> 滑冰
                        <input type="checkbox" name="" value="3" checked={hobby.includes("3")} onChange={changeHandler(setHobby)}/> 潜水
                        <input type="checkbox" name="" value="4" checked={hobby.includes("4")} onChange={changeHandler(setHobby)}/> 撑杆跳

                    </p>
                    <p><button>提交</button></p>
                </form>
            </div>
        </div>
    )
}

7-3-2. 非受控组件

import React, { useRef, useState } from 'react'

export default function App() {
    // 定义状态数据
    let [username, setUsername] = useState('atguigu')
    let [pwd, setPwd] = useState('123')
    let [sex, setSex] = useState("1")
    let [city, setCity] = useState('1')
    let [hobby, setHobby] = useState(["1","3"])

    // 定义ref
    const divRef = useRef()
    const usernameRef = useRef()
    const pwdRef = useRef()
    const sexRef = [useRef(), useRef()]
    const cityRef = useRef()
    const hobbyRef = [useRef(), useRef(), useRef(), useRef()]


    const submitHanlder = (e)=>{
        e.preventDefault()
        console.log('非受控获取提交数据')
        console.log('username: ', usernameRef.current.value.trim())
        console.log('pwd: ', pwdRef.current.value.trim())
        console.log('sex: ', sexRef.find(item=>item.current.checked).current.value)
        console.log('city: ', cityRef.current.value)
        console.log('hobby: ', hobbyRef.filter(item=>item.current.checked).map(item=>item.current.value))
    }
    return (
        <div>
            <h3>App</h3>
            <div ref={divRef}>div</div>
            <p><button onClick={()=>{
                console.log('div dom: ', divRef.current)
            }}>获取真实Dom</button></p>

            <div>
                <form action="" onSubmit={submitHanlder}>
                    <p>username: <input type="text" name="" defaultValue={username} ref={usernameRef}/></p>
                    <p>pwd: <input type="text" name="" defaultValue={pwd} ref={pwdRef}/></p>
                    <p>性别:
                        <input type="radio" name="sex" ref={sexRef[0]} value="1" defaultChecked={sex==="1"}/><input type="radio" name="sex" ref={sexRef[1]} value="0" defaultChecked={sex==="0"}/></p>
                    <p>city: 
                        <select name="" defaultValue={city} ref={cityRef}>
                            <option value="1">北京</option>
                            <option value="2">上海</option>
                            <option value="3">深圳</option>
                        </select>
                    </p>
                    <p>爱好:
                        <input type="checkbox" name="" defaultChecked={hobby.includes("1")} value="1" ref={hobbyRef[0]}/> 台球
                        <input type="checkbox" name="" defaultChecked={hobby.includes("2")} value="2" ref={hobbyRef[1]}/> 滑冰
                        <input type="checkbox" name="" defaultChecked={hobby.includes("3")} value="3" ref={hobbyRef[2]}/> 潜水
                        <input type="checkbox" name="" defaultChecked={hobby.includes("4")} value="4" ref={hobbyRef[3]}/> 撑杆跳

                    </p>
                    <p><button>提交</button></p>
                </form>
            </div>
        </div>
    )
}

7-4. Hook使用规则

  1. React钩子函数,不能在类组件中调用
  2. React Hooks必须在React函数组件或自定义React Hook函数中调用
  3. hook函数使用时数量必须是确定的【要在顶级作用域中使用】
import React, { useState,Component } from 'react'

function App() {
    let [count,setCount] = useState(10)
    // 报错:hook函数不能在普通函数中使用
    // function addTodo(){
    //     let [msg, setMsg] = useState('atguigu')
    // }

    // 不能在循环、判断中使用,必须是确定数量的【完全相同的顺序】
    // if(true){
    //     let [msg, setMsg] = useState('atguigu')
    // }
    // for(let i=0;i<100;i++){
    //     let [msg, setMsg] = useState('atguigu')
    // }
    // if(count == 1){
    //     return
    // }
    // let [msg, setMsg] = useState('atguigu')

    
    return (
        <div>App</div>
    )
}


// 报错: React Hook "useState" cannot be called in a class component.  React钩子“useState”不能在类组件中调用
// React Hooks must be called in a React function component or a custom React Hook function  React Hooks必须在React函数组件或自定义React Hook函数中调用
// 
// class App extends Component{
    
//     render(){
//         let [msg, setMsg] = useState('atguigu')
//         return (
//             <div>App</div>
//         )
//     }
// }


export default App

7-5. 自定义hook函数

hook 本质就是一个函数,hook函数和普通函数有什么区别呢?

区别在函数名

hook函数:函数名必须是以 useXxx开头的。 例如: usePostion、useMyHook

import { useEffect, useState } from "react";

/**
 * 遵循 useXxx命名法,react就认为是hook函数。
 * 在自定义hook中使用 其他hook,不报错
 */
export default function useMyHook(){
    let [x, setX] = useState(0)
    useEffect(()=>{
        console.log('sjdfjlkas')
    })
}


/**
 * 既不是函数组件【首字母没大写】、也不是其他hook【没有use开头】
 * 所以,内部不能使用 其他hook
 */
// export default function myHook(){
//     let [x, setX] = useState(0)
//     useEffect(()=>{
//         console.log('sjdfjlkas')
//     })
// }

7-6. context

祖先组件向后代组件传参

使用步骤:

  1. 创建并导出 context:export default React.createContext()

  2. 使用context.Provider组件包裹后代组件,并传递数据

    <context.Provider value={{数据}} >
    	后代组件
    </context.Provider>
    
  3. 在后代组件中获取数据

    1. 引入context: import context from './context.js'

    2. 使用useContext获取值

      let {数据} = useContext(context)
      
  • context.js
import React from 'react'
// 创建一个context对象并导出
export default React.createContext()
  • App.jsx
import React from 'react'
import Father from './components/Father'
import context from './context'
export default function App() {
    return (
        <div style={{ width: 500, height: 500, border: '1px solid red' }}>
            {/* 使用context中的Provider组件包裹,后代组件,并传递数据value */}
            <context.Provider value={{username:'atguigu'}}>
                <h3>App</h3>
                <Father></Father>
            </context.Provider>
        </div>
    )
}
  • 后代组件中
import React, { useContext } from 'react'
import Son from './Son'
// 导入context对象
import context from '../context'
export default function Father() {
    let { username } = useContext(context)
    return (
        <div style={{ width: 300, height: 300, border: '1px solid blue' }}>
            <h4>Father</h4>
            <p>username: {username}</p>
            <hr />
            <Son />
        </div>
    )
}

7-7. pubsub

作用:实现任意组件间的通信

原理:利用发布订阅原理

使用:

  1. 安装: npm i pubsub-js

  2. 导入:import PubSub from 'pubsub-js'

  3. Api

    1. 订阅消息: let 消息id = PubSub.subscribe(消息名, (消息名, 数据)=>{})
    2. 发布消息: PubSub.publish(消息名, 数据)
    3. 取消订阅:
      1. 取消自己的订阅: PubSub.unsubscribe(消息id)
      2. 取消该消息名的所有消息: PubSub.unsubscript(消息名)
      3. 取消所有消息:clearAllSubscriptions

8. ajax

  1. 安装axios
  2. 配置:
  3. 使用:
  • src/request/index.jsx 对axios进行全局配置
// 2. 导入axios
import axios from 'axios'
// 3. 配置axios
const request = axios.create({
    baseURL: 'https://api.github.com/',
    timeout: 20000
})
// 4. 配置响应拦截器-简化响应的数据
request.interceptors.response.use(response=>response.data)

export default request
  • 在组件中使用 App.jsx
import React, { useEffect } from 'react'
import request from './request'

export default function App() {

    // 一般首屏数据渲染,会在componentDidMount生命周期中发送ajax请求
    useEffect(() => {
        // useEffect的回调不能用async修饰,需要单独定义一个async函数并调用
        async function getRepo() {
            let res = await request.get('/search/repositories', {
                params: {
                    q: 'vue',
                    sort: 'stars'
                }
            })
            console.log('res: ', res)
        }
        getRepo()
    }, [])
    return (
        <div>App</div>
    )
}

9. 路由

10. redux

大型项目,组件间进行数据通信,数据的管理和维护比较麻烦。我们希望对数据进行统一的管理。使用redux

redux: 统一进行数据状态管理的库。

10-1. 三个核心概念:

10-1-1. store: 数据仓库

10-1-2. reducer[执行者]:专门操作仓库数据的函数

10-1-3. action: 提出修改数据的需求

10-2. redux基本使用

  1. 安装:npm install @reduxjs/toolkit

  2. 导入:import {createSlice} from '@reduxjs/toolkit'

  3. 创建 切片

// 1. 安装 :
// 2. 导入 createSlice 模块
//  create 创建
//  Slice  切片【模块】
import {createSlice} from '@reduxjs/toolkit'

// 3. 创建切片
const countSlice = createSlice({
    // 切片名
    name:'count',
    // initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
    initialState:{
        num:1000,
        msg:'atguigu'
    },
    // 干活的程序员[ 函数 ]
    // 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
    // 该方法的身份是actionCreator,作用是用来创建action对象的
    reducers:{
        /**
         * 
         * @param {*} state :状态数据
         * @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据} 
         *  type:属性给程序看的,不是给程序员使用的
         */
        addNum: (state, action)=>{
            state.num += action.payload
        },
        decNum:(state,action)=>{
            state.num -= action.payload
        }
    }
})

console.log('countSlice: ',countSlice)
// 解构出 actionCreator : addNum
const {addNum} = countSlice.actions

let res = addNum(1000) // 使用addNum actionCreator 创建一个action
console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
  1. 创建仓库
// 1. 安装 :
// 2. 导入 createSlice 模块
//  create 创建
//  Slice  切片【模块】
//  : 创建数据仓库的模块
import {createSlice, configureStore} from '@reduxjs/toolkit'
// 3. 创建切片
const countSlice = createSlice({
    // 切片名
    name:'count',
    // initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
    initialState:{
        num:1000,
        msg:'atguigu'
    },
    // reducers中定义:干活的程序员[ 函数 ]
    // 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
    // 该方法的身份是actionCreator,作用是用来创建action对象的
    reducers:{
        /**
         * 
         * @param {*} state :状态数据
         * @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据} 
         *  type:属性给程序看的,不是给程序员使用的
         */
        addNum: (state, {payload})=>{
            state.num += payload
        },
        decNum:(state,action)=>{
            state.num -= action.payload
        }
    }
})

// 以下是打印测试切片的代码
// console.log('countSlice: ',countSlice)
// // 解构出 actionCreator : addNum
const {addNum} = countSlice.actions

// let res = addNum(1000) // 使用addNum actionCreator 创建一个action
// console.log('res: ', res) // {type: 'count/addNum', payload: 1000}

// 创建仓库 store
const store = configureStore({
    reducer:{
        count:countSlice.reducer
    }
})

// 可以对状态数据进行监听,如果有变化,就触发回调函数的执行
store.subscribe(()=>{
    console.log('订阅数据变了')
    console.log(store.getState())
})

// 查看状态数据
// console.log(store.getState())

// 修改数据
// dispatch: 分发,参数是一个action对象,根据action对象中的 type,来找到对应切片中的对象应发完成任务
store.dispatch(addNum(30))

store.dispatch(addNum(50))

store.dispatch(addNum(100))

10-3. 添加商品

/**
 *  使用redux 管理商品数据
 *  1. 可以添加商品数据
 * 
 */

import {createSlice, configureStore} from '@reduxjs/toolkit'

// 1. 创建切片
const goodsSlice = createSlice({
    name:'goods',
    initialState:{
        goodsList:[]
    },
    reducers:{
        addGoods(state, {payload}){
            // 实现向goodsList数组中添加数据
            // console.log('payload', payload)
            let goods = {
                ...payload,
                // 0-9 a-z = 36
                // 0.1231532234547643
                id: Math.random().toString(36).slice(2)
            }
            // console.log('goods', goods)
            state.goodsList.push(goods)
        }
    }
})

// 2. 创建仓库
const store = configureStore({
    reducer:{
        goods: goodsSlice.reducer
    }
})

// 3. 监听仓库
store.subscribe(()=>{
    console.log('数据: ', store.getState())
})

// 4. 添加商品
// 获取actionCreator
const {addGoods} = goodsSlice.actions

// {
//     gname:'商品名',
//     price:1999
// }

store.dispatch(addGoods({gname:'小米手机', price:1999}))

store.dispatch(addGoods({gname:'华为笔记本', price:7000}))

10-4. store模块化

src

|- store 整个redux数据目录

​ | |- slice 所有切片目录

| |- index.js 创建的store仓库入口文件

  • store/slice/goodsSlice
import { createSlice } from '@reduxjs/toolkit'

// 1. 创建切片
const goodsSlice = createSlice({
    name: 'goods',
    initialState: {
        goodsList: []
    },
    reducers: {
        addGoods(state, { payload }) {
            // 实现向goodsList数组中添加数据
            // console.log('payload', payload)
            let goods = {
                ...payload,
                // 0-9 a-z = 36
                // 0.1231532234547643
                id: Math.random().toString(36).slice(2)
            }
            // console.log('goods', goods)
            state.goodsList.push(goods)
        }
    }
})

/**
 * 一个切片,大体暴露以下几类东西
 * 1. 默认暴露: reducer
 * 2. 分别暴露:actionCreator:
 * 3. 分别暴露:异步操作的方法
 */
export default goodsSlice.reducer
export const {addGoods} = goodsSlice.actions
  • store/index.js
import {configureStore} from '@reduxjs/toolkit'
import goods from './slice/goodsSlice'
// 2. 创建仓库
const store = configureStore({
    reducer: {
        goods
    }
})

export default store
  • 调用使用: src/index.js
// 1. 导入仓库
import store from "./store";
// 2. 导入actionCreator
import {addGoods} from './store/slice/goodsSlice'

store.subscribe(()=>{
    console.log('数据: ', store.getState())
})

// 2. 添加商品

store.dispatch(addGoods({
    gname:'小米手机',
    price:1999
}))

store.dispatch(addGoods({
    gname:'华为手机',
    price:5999
}))

11. redux小练习

需求:

点击按钮发送ajax请求,获取一句话,将一句话的长度累计到 redux状态数据

文件介绍:

  1. request/index.js: axios 统一配置文件【基础路径、响应拦截器简化返回数据】
  2. api/onSentence.js: 所有跟一句话相关的请求接口方法
  3. store: 数据仓库
  4. store/slice/countSlice:
    1. 定义状态数据num
    2. 使用createAsyncThunk 创建了异步的actionCreate
      1. 调用了api接口
    3. extraReducer中,添加了case,处理请求后的数据
  5. App.jsx
    1. 展示状态数据
    2. 点击按钮,触发异步的actionCreator方法
  6. src/index.js
    1. 导入Provider
    2. 包裹根组件,传递store数据

12-redux-todoslist-综合练习

todos数据来源于数据库,在前端使用redux进行管理。因此,需要保证远端服务器数据和redux中的数据是同步的。因此,在进行用户交互操作的时候, 如果改动了数据,需要修改远端数据库的数据,也要修改redux中管理的数据。

  1. 修改远端服务器数据:在异步的actionCreator中进行
  2. 修改redux中的数据:在addCase中进行

12-1. request和api的封装

  1. request/index.js
import axios from 'axios'
// 配置baseURL和超时时间
const request = axios.create({
    baseURL:'http://localhost:7000',
    timeout:20000
})
// 响应拦截器,简化服务器端返回数据
request.interceptors.response.use(response=>response.data)
export default request
  1. src/api/todos.js

    根据后端接口文档【或源码】,封装前端发送ajax的请求函数

import request from '../request'
/**
 * 获取素有的todolist数据
 * @returns 
 */
export const getTodos = ()=>{
    return request.get('/todos')
}
/**
 * 添加todo
 * @param {*} todo  {title:xxx,isDone:false}
 * @returns 
 */
export const addTodo = (todo)=>{
    return request.post('/todos', todo)
}
/**
 * 根据id删除todo
 * @param {*} _id 
 * @returns 
 */
export const deleteById = (_id)=>{
    return request.delete('/todos/' + _id)
}
/**
 * 根据id 修改isDone的值
 * @param {*} todo  {_id:xxx, title:xxx, isDone:false}
 * @returns 
 */
export const updateIsDone = (todo)=>{
    return request.patch('/todos/'+ todo._id, todo)
}

/**
 * checkAll ,全选反选
 * @param {*} data  {isDone: false/true}
 * @returns 
 */
export const checkAll = (data)=>{
    return request.patch('/checkAll', data)
}

/**
 * 清除已完成
 * @returns 
 */
export const deleteChecked = ()=>{
    return request.get('/deleteChecked')
}

12-2. 首屏数据渲染

获取服务器端 todoList数据,初始化redux中的状态数据。在组件中读取 redux中的todos数据,进行渲染

12-2-1. 创建store

  1. store/index.js
import { configureStore } from "@reduxjs/toolkit";
import todoList from './slice/todoSlice'
const store = configureStore({
    reducer:{
        todoList
    }
})
export default store
  1. store/slice/todoSlice.js

    1. 定义异步的actionCreator: 发送ajax请求,获取所有todos数据
    2. addCase: 将请求回来的todos数据,初始化redux中的 状态数据 todos
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit'
// 导入api方法
import {getTodos} from '../../api/todos'
const todoSlice = createSlice({
    name:'todoList',
    initialState:{
        todos:[]
    },
    extraReducers:builder=>{
        // 初始化todos
        builder.addCase(asyncGetTodos.fulfilled, (state, {payload})=>{
            state.todos = payload
        })
    }
})

/**
 * 发送ajax请求 初始化 todos
 */
export const asyncGetTodos = createAsyncThunk('todoList/getTodo', async (payload)=>{
    let {todos} = await getTodos()
    return todos
})

export default todoSlice.reducer
  1. src/index.js

导入Provider,包裹App根组件, 传递store

import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
    <Provider store={store}>
        <App />
    </Provider>
)

12-2-2. 组件加载完之后,compomentDidMount中,发送ajax请求

  1. App.jsx 中
export default function App() {
    const dispatch = useDispatch()
    useEffect(()=>{
        // 初始化todos
        dispatch(asyncGetTodos())
    },[])
    return (
        <>
            <TodoList />
        </>
    )
}

  1. List组件中,获取redux中的todos,并进行遍历渲染
import React from 'react'
import { useSelector } from 'react-redux'
import Item from '../Item/Item'

export default function List() {
    let {todos} = useSelector(state=>state.todoList)
    return (
        <ul className="todo-main">
            {todos.map(todo=><Item key={todo._id} todo={todo}/>)}
        </ul>
    )
}

13. 其他hook

13-1. useRef-React.forwardRef-useImpretiveHandle

useRef:

  1. 绑定dom元素,获取真实dom

  2. 可以单独模拟componentDidUpdate

  3. 给以给类组件绑定,获取类组件的实例对象

    1. 给函数组件绑定:

      4-1. 本身不支持

      4-2. 函数组件使用 React.forwardRef 包裹再导出

      ​ 4-2-1. 函数组件的第二个参数,可以收到父组件的ref对象

      ​ 4-2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法

  • App.jsx
import React, { useRef } from 'react'
import { useEffect } from 'react'
import ClassTest from './components/ClassTest'
import FunTest from './components/FunTest'

/**
 *  1. 绑定dom元素,获取真实dom
 *  2. 可以单独模拟componentDidUpdate
 *  3. 给以给类组件绑定,获取类组件的实例对象
 *  4. 给函数组件绑定:
 *      1. 本身不支持
 *      2. 函数组件使用 React.forwardRef 包裹再导出
 *          2-1. 函数组件的第二个参数,可以收到父组件的ref对象
 *          2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法
 * 
 */
export default function App() {
    let flagRef = useRef(true)
    console.log(flagRef)
    useEffect(() => { // componentDidMount + componentDidUpdate
        if (flagRef.current) {
            // didMount
            flagRef.current = false
            return;
        }
        // didUpdate
    })

    // 类组件ref
    let classRef = useRef()
    // 函数组件ref
    let funRef = useRef('fun')
    return (
        <div>
            <h3>类组件</h3>
            <ClassTest ref={classRef} />
            <hr />
            <h3>函数组件</h3>
            {/* 函数组件本身不能绑定ref */}
            {/* 
                可以使用React.forwordRef处理函数组件后,进行ref的绑定 
                可以在父组件中操作子组件的真实dom元素
            */}
            <FunTest ref={funRef} />
            <button onClick={() => {
                console.log(classRef.current) // ref可以绑定给类组件获取类组件的实例对象
                // 
                console.log(funRef.current)
                funRef.current.changeColor()

                funRef.current.changeSize()
            }}>获取ref</button>
        </div>
    )
}
  • ClassTest.jsx
import React, { Component } from 'react'

export default class ClassTest extends Component {
    state = {
        msg:'类组件'
    }
    render() {
        return (
            <div>ClassTest</div>
        )
    }
}
  • FunTest.jsx
import React,{useImperativeHandle, useRef} from 'react'

function FunTest(props,ref) {
    console.log('ref: ', ref)
    // 子组件自己的ref
    let selfRef = useRef()
    useImperativeHandle(ref, ()=>({
        changeColor:()=>{
            selfRef.current.style.color = 'blue'
        },
        changeSize:()=>{
            selfRef.current.style.fontSize = '100px'
        }
    }))
    return (
        <div ref={selfRef}>FunTest</div>
    )
}

export default React.forwardRef(FunTest)

13-2. useReducer[了解]

模拟一个小型的 redux,做简易版的集中状态管理

  • App.jsx
import React from 'react'
import Test1 from './components/Test1'

export default function App() {
  return (
    <div>
        <Test1/>
    </div>
  )
}
  • Test1.jsx
import React, { useReducer } from 'react'

const initialState = { count: 0, msg: '哈哈' }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'decrement':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      throw new Error()
  }
}
export default function Test1() {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <>
      Count: {state.count} <br />
      msg: {state.msg} <br />
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  )
}

13-3. useCallback 【掌握】

避免组件更新,函数重新创建。

setXxx 时,要使用 回到函数,获取到最新的状态数据

import React, { useCallback, useState } from 'react'
/**
 *  useCallBack 可以缓存函数,避免组件更新,函数重新创建
 * 
 */
export default function App() {
    let [count, setCount] = useState(100)
    function addCount(){
        setCount(count=>{
            return count + 1
        })
    }
    const handle = useCallback(addCount, [])
    return (
        <div>
            <p>count: {count}</p>
            <p><button onClick={handle}>count + </button></p>
        </div>
    )
}

13-4. useMemo【掌握】

缓存一个函数运行的结果

import { useMemo, useState } from "react";

export default function After() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return <div>
        <h4>{count}-{expensive}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

13-5. React.memo【掌握】

类似类组件的纯组件功能,当自身状态数据state和外部数据props没有变化的时候,不重新渲染

  • App.jsx
import React, { useState,Component } from 'react'
import Son from './components/Son'

/**
 * 
 * 函数组件自身状态数据没有变化,已经做过优化,只多渲染一次
 */
export default class App extends Component {
    state = {
        count:100
    }
    render(){
        let {count} = this.state
        console.log('app render')
        return (
            <div>
                <p>count:{count}</p>
                <Son count={count}/>
                <p><button onClick={()=>{
                    this.setState({
                        count:111
                    })
                }}>count+</button></p>
            </div>
        )
    }
}

  • Son.jsx
import React from 'react'
import { useState } from 'react'
/**
 *  函数组件对state,自身状态数据,不变时,只多渲染一次
 *  函数组件没有对props数据不变的情况做优化
 *
 */
function Son({count}) {
    let [msg, setMsg] = useState('atguigu')
    console.log('son render')
    return (
        <div>
            <p>props-count: {count}</p>
            <p>state-msg: {msg}</p>
            <p><button onClick={()=>{
                setMsg('atguigu123123')
            }}>msg- change</button></p>
        </div>
    )
}

export default React.memo(Son)

13-6. useLayoutEffect[了解]

优化页面呈现,避免出现位置移动的闪屏

  • Animate.jsx
import React, { useEffect, useLayoutEffect, useRef } from 'react'
import TweenMax from 'gsap' // npm i gsap@3.7.0
import './index.css'

const Animate = () => {
  const REl = useRef(null)
  useLayoutEffect(() => {
    /*下面这段代码的意思是当组件加载完成后,在0秒的时间内,将方块的横坐标位置移到600px的位置*/
    TweenMax.to(REl.current, 0, { x: 600 })
  }, [])
  return (
    <div className="animate">
      <div ref={REl} className="square">
        square
      </div>
    </div>
  )
}

export default Animate
  • index.css
.square{
    width:100px;
    height:100px;
    border:2px solid red;
}

附录

1. react代码片段

{
	"react模板":{
		"prefix": "!react",
		"body": [
			"<!DOCTYPE html>",
			"<html lang=\"en\">",
			"<head>",
				"\t<meta charset=\"UTF-8\">",
				"\t<title>Title</title>",
				"\t<script src=\"./lib/react.development.js\"></script>",
				"\t<script src=\"./lib/react-dom.development.js\"></script>",
				"\t<script src=\"./lib/babel.min.js\"></script>",
			"</head>",
			"<body>",
			"\t<div id=\"root\"></div>",
			"</body>",
			"<script type=\"text/babel\">",
			"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
			"\troot.render((",
				"\t\t<div></div>",
			"\t))",
			"</script>",
			"</html>"
		],
		"description": "快速构建react模板页页面"
	}
}

1-3. 注意事项

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
</head>

<body>
    <div id="root"></div>
    <div class="box"></div>
</body>
<script>
    {
        // 1. 可以在同一个页面有多个容器
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render('root容器')

        // const box = ReactDOM.createRoot(document.querySelector('.box'))
        // box.render('box 容器')
    }
    {
        // 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render('首次渲染')
        // root.render('二次渲染')
    }
    {
        // 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
        // const root1 = ReactDOM.createRoot(document.querySelector("#root"));
        // const root2 = ReactDOM.createRoot(document.querySelector("#root"));
        // root1.render('root 一')
        // root2.render('root 二')
    }
    {
        // 4. 不能将 html body 指定为 react容器
        // html
        // const root = ReactDOM.createRoot(document.documentElement);
        // root.render('html')
        // 
        // const root = ReactDOM.createRoot(document.body);
        // root.render('body')
    }
    {
        // 5. 支持链式调用
        ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
    }
</script>

</html>

1-3. 虚拟DOM和真实DOM

<script>
    /**
     *  react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
     *  1. 页面是如何渲染的?
     *     地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
     *     1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
     *     1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
     *     1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
     *     
     *  2. 什么是重排和重绘?
     *     css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
     * 
     *  3. 传统DOM操作的缺陷!
     *     频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
     *     this.style.height = 100px;
     *     this.style.left = '200px'
     *     
     *  4. React、vue 性能更好,为什么?
     *     React 和 vue引入了虚拟dom 优化性能
     *     
     *  5. 虚拟DOM是什么?
     *     1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
     *     2. 虚拟DOM 通过 render 可以转化为真实DOM
     *     3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
     *     4. 虚拟dom 在react中,我们又称之为时 react元素 
     *     5. 虚拟dom的创建有两种方法
     *         1. 通过 React.createElement() 创建
     *         2. 通过 jsx 创建
     * 
     */
</script>

1-4. call apply bind区别

call apply 立即执行

bind:返回一个新函数,不执行

let obj = {name:'atguigu'}

function getSonData(a){
    console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])

// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()

1-5. 快速创建 类组件和函数组件的快捷键

rfc: react function component

rcc: react class component

1-6. class-普通方法-箭头函数方法

普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。

直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系

class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;


        // this.eat = ()=>{
        //     console.log(this)
        //     console.log(this.name + '想吃饭了')
        // }
        // this.state = {
        //     count:1
        // }
    }
    // say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
    say(){
        console.log('say this: ', this)
        console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
    }

    // 通过=号赋值,定义箭头函数
    // eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
    eat = ()=>{
        console.log('eat this: ', this)
        console.log(this.name + '想吃饭了')
    }
    state = {
        count:1
    }

}

const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()

p1.eat()
p2.eat()

//console.log('say: ',p1.say === p2.say) // true  say方法在 Perons.prototype.say

//console.log('eat: ',p1.eat === p2.eat)// false  每一个实例对象都有一个自己的eat方法


let obj = {
    name:'ll',
    f1:p1.say,
    f2:p1.eat
}

// obj.f1() // 我的名字是: ll, 我的年龄是: undefined
// say 方法中的this,谁调用this指向谁。

obj.f2()


## 1-3. 注意事项

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./lib/react.development.js"></script>
    <script src="./lib/react-dom.development.js"></script>
</head>

<body>
    <div id="root"></div>
    <div class="box"></div>
</body>
<script>
    {
        // 1. 可以在同一个页面有多个容器
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render('root容器')

        // const box = ReactDOM.createRoot(document.querySelector('.box'))
        // box.render('box 容器')
    }
    {
        // 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
        // const root = ReactDOM.createRoot(document.querySelector("#root"));
        // root.render('首次渲染')
        // root.render('二次渲染')
    }
    {
        // 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
        // const root1 = ReactDOM.createRoot(document.querySelector("#root"));
        // const root2 = ReactDOM.createRoot(document.querySelector("#root"));
        // root1.render('root 一')
        // root2.render('root 二')
    }
    {
        // 4. 不能将 html body 指定为 react容器
        // html
        // const root = ReactDOM.createRoot(document.documentElement);
        // root.render('html')
        // 
        // const root = ReactDOM.createRoot(document.body);
        // root.render('body')
    }
    {
        // 5. 支持链式调用
        ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
    }
</script>

</html>

1-3. 虚拟DOM和真实DOM

<script>
    /**
     *  react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
     *  1. 页面是如何渲染的?
     *     地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
     *     1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
     *     1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
     *     1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
     *     
     *  2. 什么是重排和重绘?
     *     css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
     * 
     *  3. 传统DOM操作的缺陷!
     *     频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
     *     this.style.height = 100px;
     *     this.style.left = '200px'
     *     
     *  4. React、vue 性能更好,为什么?
     *     React 和 vue引入了虚拟dom 优化性能
     *     
     *  5. 虚拟DOM是什么?
     *     1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
     *     2. 虚拟DOM 通过 render 可以转化为真实DOM
     *     3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
     *     4. 虚拟dom 在react中,我们又称之为时 react元素 
     *     5. 虚拟dom的创建有两种方法
     *         1. 通过 React.createElement() 创建
     *         2. 通过 jsx 创建
     * 
     */
</script>

1-4. call apply bind区别

call apply 立即执行

bind:返回一个新函数,不执行

let obj = {name:'atguigu'}

function getSonData(a){
    console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])

// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()

1-5. 快速创建 类组件和函数组件的快捷键

rfc: react function component

rcc: react class component

1-6. class-普通方法-箭头函数方法

普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。

直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系

class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;


        // this.eat = ()=>{
        //     console.log(this)
        //     console.log(this.name + '想吃饭了')
        // }
        // this.state = {
        //     count:1
        // }
    }
    // say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
    say(){
        console.log('say this: ', this)
        console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
    }

    // 通过=号赋值,定义箭头函数
    // eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
    eat = ()=>{
        console.log('eat this: ', this)
        console.log(this.name + '想吃饭了')
    }
    state = {
        count:1
    }

}

const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()

p1.eat()
p2.eat()

//console.log('say: ',p1.say === p2.say) // true  say方法在 Perons.prototype.say

//console.log('eat: ',p1.eat === p2.eat)// false  每一个实例对象都有一个自己的eat方法


let obj = {
    name:'ll',
    f1:p1.say,
    f2:p1.eat
}

// obj.f1() 
// say 方法中的this,谁调用this指向谁。

obj.f2()