React

  • react把真实DOM树转换为虚拟DOM树(因为操作真实DOM的性能消耗大),根据DOM Diff 算法,对比新旧虚拟DOM树,然后再更新到真实DOM树。

创建React项目:

Step1:全局安装create-react-app脚手架
npm install -g create-react-app Step2:创建一个项目 create-react-app 项目名称

函数式组件和类式组件

  • 函数式组件
js 复制代码
// 函数式组件 16.8之前么有hooks,是无状态组件 如点击不能加1
function App() {
    return (
        <div style={{ background: 'pink' }} className="test">fighting Evelyn!</div>
    )
}
export default App
  • 类式组件
js 复制代码
class App extends React.Component {
    render() {
        // JSX语法
        // 要求必须有唯一根标签
        return (
            <div>hello Evelyn!</div>
            // 在组件内部,可以在插值内部使用js表达式:
        )
    }
}

react中绑定事件

js 复制代码
import React, { Component } from 'react'

export default class EventTest extends Component {
    name = 'Evelyn'
    render() {
        return (
            <div>
                <button onClick={() => {
                    // 箭头函数中,this为当前类组件实例对象
                    console.log(this) 
                }}>测试this</button>
                
                <button onClick={function () {
                    // 普通函数中,this为undefined
                    console.log(this) 
                }}>测试this2</button>
                
                <button onClick={this.clickHandler1.bind(this)}>test1</button>
                {/* 普通函数,需要使用bind修改this指向 */}

                <button onClick={this.clickHandler2}>test2</button>
                {/* 箭头函数 */}
                
                {/* 最佳推荐方案,适合传参使用 */}
                <button onClick={() => {
                    this.clickHandler1()
                 }>最佳绑定事件方案</button>
            </div>
        )
    }

    // 普通函数
    clickHandler1() {
        console.log(this) // undefined
    }

    // 箭头函数
    clickHandler2 = () => {
        console.log(this) // 当前类组件实例对象
    }
}

【注】:其中最推荐的写法是,使用箭头函数包裹,后期传参很方便。

在react中绑定事件,不是绑定在某一个DOM元素上,而是绑定在根节点身上,采用的是事件代理,

ref

React中之前是使用ref,this.refs用来获取当前实例对象,

js 复制代码
<input ref="inputRef"></input>
<button onClick={() => {
    console.log(this.refs.inputRef)
}}>点击按钮获取当前输入框</button>

但是React舍弃了这种获取当前绑定DOM元素的方法, 使用React.createRef()获取,获取的时候采用this.ref名称.current

js 复制代码
myRef = React.createRef()
render() {
    return (
        <div>
            <input ref={this.myRef}></input>
            <button onClick={this.handleClick}>添加</button>
        </div>
    )
}

handleClick = () => {
    console.log(this.myRef.current.value)
    this.myRef.current.value = ''
}

状态

因为react中没有使用Object.defineProperty进行数据代理, 所以使用状态数据state实现响应式,使用setState进行修改响应式数据。

js 复制代码
import React, { Component } from 'react'

export default class ChangeState extends Component {
  state = {
    isClick: false
  }
  render() {
    return (
      <div>
        <button onClick={() => {
          // this.state.isClick = !this.state.isClick
          // 警告:不要直接修改state状态数据!
          // 修改状态数据使用 setState的函数
          this.setState({
            isClick: !this.state.isClick
          })
        }}>{this.state.isClick ? '收藏' : '取消收藏'}</button>
      </div>
    )
  }
}

列表遍历

使用map方法进行遍历

js 复制代码
export default class List extends Component {
    state = {
        list: ['aa', 'bb', 'cc']
    }
    render() {
        return (
            <ul>
                {
                    this.state.list.map((item, index) => <li key={index}>{item}</li>)
                }
            </ul>
        )
    }
}

TodoList案例

设置一个todoList数组的状态值,每次点击添加,结合ref属性获取到输入框中的内容,将该内容push到todoList数组中,一定要记得是,使用setState修改state数据。

js 复制代码
export default class RefTest extends Component {
    myRef = React.createRef()
    state = {
        todoList: ['11', '22', '33']
    }
    render() {
        return (
            <div>
                <input ref={this.myRef}></input>
                <button onClick={this.handleClick}>添加</button>
                <hr></hr>
                <ul>
                    {
                        // this.state.todoList.map((item, index) => <li key={index}>{item} <button onClick={() => {
                        //     this.handleDelClick(index)
                        // }}>删除</button></li>)

                        this.state.todoList.map((item, index) => <li key={index}>{item} <button onClick={this.handleDelClick.bind(this, index)}>删除</button></li>)
                    }
                </ul>
            </div>
        )
    } 

    handleClick = () => {
        console.log(this.myRef.current.value)

        this.state.todoList.push(this.myRef.current.value)

        this.setState({
            todoList: this.state.todoList
        })

        this.myRef.current.value = ''
    }


    handleDelClick(idx){
        console.log(idx)
        // 首先复制一份数组,不要修改原数组
        // 数组原型上的slice,concat方法,都是浅拷贝,不会影响到原数据
        let newList = this.state.todoList.slice()
        newList.splice(idx, 1)
        this.setState({
            todoList: newList
        })
    }
}

条件渲染

通常使用三元运算符逻辑与逻辑或进行条件渲染!

setState同步&异步?

当使用setState修改状态值:

  • 当setState处在同步逻辑中,异步更新状态,异步更新真实DOM;
  • 当setState处在异步逻辑中,同步更新状态,同步更新真实DOM

(比如在绑定事件的回调定时器中的回调(超时时间设置为0) 是异步的,因为react为了节约性能,会把多次setState合并为一次进行,而在最后一次性的更新state。)

setState函数提供了第二个参数,是一个回调,状态和DOM更新完之后就会被触发(有点类似于Vue中的nextTick),然后执行回调中的代码(比如某一些操作要在DOM更新完之后再去执行)。 (案例:BetterScroll)

属性------props

state状态值是组件内部使用,其他组件不能使用 而属性是由父组件传递过来,this.props,组件之间传递数据,通过标签属性的形式 如果是字符串,直接引号, 如果是JS表达式,使用大括号插值语法

函数式组件:通过函数形参,得到一个对象,直接解构 类式组件:this.props

属性的验证

使用类属性,类式组件.prototype = { 数据1:验证方法 , 数据2:验证方法 } import propTypes from 'prop-types' 得到的propTypes是一个对象,里边包含了很多验证数据类型的方法。

属性和数据

状态和属性改变,都会造成render函数的重新调用, 不允许在子组件中直接修改父组件传递过来的属性!!!

受控组件和非受控组件

  • 受控组件 :组件中的表单项根据state状态数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中,也就是实现了页面表单元素 与 state 数据的双向绑定(推荐使用)
  • 非受控组件:不与state数据相关联,需要手动读取表单元素的值(借助于ref属性)
js 复制代码
// 非受控组件
export default class Test extends Component {
    ipt = React.createRef()
    render() {
        return (
            <div>
                <input ref={this.ipt} />
                <button onClick={this.getIptContent}>获取</button>
                <button onClick={this.resetIptContent}>重置</button>
            </div>
        )
    }
    getIptContent = () => {
        // 非受控组件 如何获取到input输入框中的内容?
        // 获取到该ref对象身上的current属性,继而获取到value属性
        console.log(this.ipt.current.value)
    }

    resetIptContent = () => {
        // 非受控组件,如何赋值?
        this.ipt.current.value = ''
    }
}
js 复制代码
// 受控组件
export default class Test extends Component {
    // 创建初始状态值
    state = {
        uname: '',
        upass: ''
    }
    render() {
        return (
            <div>
                用户名:<input value={this.state.uname} onChange={this.saveUname} />
                密  码:<input value={this.state.upass} onChange={this.saveUpass} />
                <button onClick={() => this.login()}>登录</button>
            </div>
        )
    }

    // 涉及到this:
    // 1、普通函数,使用bind修改this指向 || 使用箭头函数包裹(记得调用)
    // 2、箭头函数(直接写)
    login() {
        console.log(this.state.uname)
        console.log(this.state.upass)
    }

    saveUname = (e) => {
        // console.log(e)
        this.setState({
            uname: e.target.value
        }, () => {
            // 写在异步函数中,可以同步获取到state状态值!
            console.log('异步', this.state.uname)
        })
        // 写在同步函数中,可以异步获取到state状态值!
        console.log('同步', this.state.uname)
    }

    saveUpass = (e) => {
        this.setState({
            upass: e.target.value
        })
    }
}

父子组件间通信

  • 父传子 ------ 传递数据
  • 子传父 ------ 传递方法(子组件通知给父组件,让父组件去修改state值)

采用的方案是:通过标签属性的形式,父传给子一个函数,父中定义函数修改state状态值,子中调用这个函数(间接实现通过父组件修改子组件)

  • 组件的ref属性

iptRef = React.createRef() 在组件上绑定ref属性,ref={this.iptRef} ,然后this.iptRef.current得到的就是这个组件实例对象

非父子组件之间的通信

  • (1)状态提升 / 中间人模式

多个子组件有同一个父组件,父组件作为一个中间人,进行数据传递。

(子组件先传递数据给父组件,通过函数传参的形式,父组件将接收到的数据通过setState设置为自己的状态值,然后通过props传递给另一个子组件。)

  • (2)发布订阅模式

subscribe方法和publish方法(Redux就是基于订阅发布的)

js 复制代码
import React, { Component } from 'react'

export default class App extends Component {
    render() {
        return (
            <div>

            </div>
        )
    }
}

let bus = {
    list: [],
    // 订阅消息
    subscribe(cb) {
        this.list.push(cb)
    },
    // 发布消息
    publish() {
        this.list.forEach(cb => {
            // 发布消息的时候可以传递形参
            cb && cb('Evelyn')
        })
    }
}

bus.subscribe((text) => {
    console.log(111, text)
})

bus.subscribe((text) => {
    console.log(222, text)
})

bus.publish()
  • (3)context状态树传参

采用生产者-消费者模式,Provider和Consumer

插槽

在父组件中调用子组件,当子组件中写了内容,这就是插槽,在子组件中通过this.props.children读取到插槽中的内容,当插槽中有多个标签时,children属性值就是一个数组,可以根据下标读取到。

使用插槽,可以在一定程度上减少父子组件之间的通信,因为在插槽中可以直接读取到父组件中数据/状态。

组件生命周期

初始化阶段
  • ComponentWillMount(组件将要挂载到真实DOM中,只执行一次,DOM上树之前,最后一次修改状态的机会)

此阶段特点:无法获取到真实DOM,可以修改state

  • Render(渲染页面)

此阶段特点:不要在render中修改state数据,会造成死循环

  • ComponentDidMount (只执行一次)

此阶段特点:初始化数据的作用(数据请求、订阅函数、事件监听、设置定时器,基于创建完成的DOM进行初始化)

运行中
  • ComponentWillReceiveProps 父组件修改属性时触发
  • ShouldComponentUpdate:返回false会阻止render调用
  • ComponentWillUpdate:不能修改属性和状态,不能获取到DOM
  • Render:只能访问this.props和this.state,不允许修改状态和DOM输出(setState)
  • ComponentDidUpdate:能够获取到DOM,做一些更新DOM的库(BetterScroller)
销毁阶段
  • ComponentWillUnmount:在删除组件之前进行收尾工作(定时器和事件监听),如定时器的timerId,在销毁阶段中清楚timerId

函数式组件

hooks------useState(管理组件状态)

js 复制代码
import React, { useState } from 'react'

export default function App() {
    // let res = useState('happy')
    // console.log(res) // ['happy' , f ]
    let [mood, setMood] = useState('happy')
    // 使用useState函数的返回值是一个数组
    // 1、mood表示一个状态值
    // 2、setMood表示修改状态值的函数
    return (
        <div>
            <button onClick={() => {
                setMood('emo')
            }}>{mood}</button>
        </div>
    )
}

hooks------useEffect(副作用函数)

当useEffect函数中:

第一个参数,必须传入一个回调函数!!

第二个参数,传值的几种情况(第二个参数表示依赖)

  • 不传(缺失依赖)

useEffect会在第一次渲染以及每次更新渲染后都执行。(第一次渲染后执行一次useEffect,当useEffect中回调函数改变了state值,而state值改变后会触发组件的重新渲染,所以就会无限循环下去。)

js 复制代码
const [count, setCount] = useState<number>(1);
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 不传值, 第 ${count} 次执行`);
});
// 打印log,无限循环
第二个参数: 不传值, 第 1 次执行
第二个参数: 不传值, 第 2 次执行
第二个参数: 不传值, 第 3 次执行
第二个参数: 不传值, 第 ... 次执行
  • 传一个空数组------相当于是ComponentDidMount(组件挂载完成)

useEffect只会在第一次渲染之后执行一次。(第一次渲染之后执行了一次useEffect,当useEffect中回调函数改变了state值,由于第二个参数是空数组,没有依赖,相当于依赖没发生变化,所以不会执行回调函数,state无更新,不触发组件的重新渲染)

js 复制代码
const [count, setCount] = useState<number>(1);
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1); 
    }, 1000);
    console.log(`第二个参数: 空数组, 第 ${count} 次执行`);
}, []);
// 打印log,执行一次
第二个参数: 空数组, 第 1 次执行
 
  • 传入一个非空数组

当依赖的值发生了改变,useEffect函数会再次执行。。。

在useEffect中,return一个回调函数,相当于是模拟ComponentWillUnmount

js 复制代码
useEffect(() => {
        return () => {
            console.log('组件将要被卸载时...')
            // 模拟组件销毁的生命周期
            // 解绑事件,清除定时器
        }
})

useCallback 记忆函数

useState是一个记忆函数,能够记住state状态值。

因为每次调用setXXX函数,修改状态值,都会导致useState函数重新调用执行,所以,为了防止因为组件的重新渲染,导致方法被重新创建,起到缓存的作用,只有当第二个参数发生了变化,才会重新声明一次。

使用useCallback()进行包裹,相当于进行缓存,可以优化性能,第二个参数是[]

useMemo 记忆组件

useCallback(fn , inputs) === useMemo(()=>fn , inputs)

  • useCallback的功能可以由useMemo所取代,也可以使用useMemo返回一个记忆函数。
  • useCallback不会执行 第一个参数函数,而是将其返回;useMemo会执行 第一个函数,并且会将函数的执行结果返回并进行赋值。
  • useCallback常用记忆事件函数,生成记忆后的事件函数传递给子组件;useMemo更适合于经过函数计算得到一个确定的值,比如一个记忆的组件(类似于Vue中的计算属性)

基于依赖数据改变了,就会重新计算

useRef

可以得到普通的DOM节点/组件实例对象 const usernameIpt = useRef() || React.createRef() 将ref属性绑定在DOM元素或标签属性身上,然后根据usernameIpt.current.value

如何保存一个变量? 使用useState,保存一个状态变量 使用useRef

useContext

在函数式组件中,简化消费者生产者方案,解决跨级通信的方案。

const GlobalContext = React.createContext() // 创建一个context对象

useReducer

(在单个组件中实现状态的管理)外部集中管理状态数据,和后边的redux很像。

React路由 根据不同的url地址展示不同的组件/内容

npm i react-router-dom(默认就是6版本)

基本语法
js 复制代码
import { BrowserRouter, Routes, Route } from 'react-router-dom'
export default class App extends Component {
    render() {
        return (
            <BrowserRouter>
                <Routes>
                    <Route path='/' element={<Film />} />
                    <Route path='/film' element={<Film />} />
                    <Route path='/cinema' element={<Cinema />} />
                    <Route path='/center' element={<Center />} />
                    <Route path='*' element={<NotFound />} />
                </Routes>
            </BrowserRouter>
        )
    }
}
重定向
  • (1)使用Navigate导航组件替代 <Route path='/' element={<Navigate to="/film" />} />

  • (2)自定义Redirect组件

js 复制代码
function Redirect({to}){
    const navigate = useNavgate()
    useEffect(()=>{
        navigate(to,{ replace : true} }
    })
    return null
}

<Route path='/film' element={<Redirect to="/film "/>} />
二级路由

【注】:如果想要在Route组件中渲染二级路由组件,需要将单标签写法的修改成双标签 <Route></Route>,然后在二级路由中,使用path和element标识对应的路由。

  • Step1:
js 复制代码
<Routes>
    <Route path='/about' element={<About />} >
        <Route path='/about/news' element={<News />} />
        <Route path='/about/message' element={<Message />} />
    </Route>
    <Route path='/home' element={<Home />} >
    	<Route path='/home/news' element={<News />} />
        <Route path='/home/message' element={<Message />} />
    </Route>
</Routes>
  • Step2:在一级路由组件中使用<Outlet/>标签进行占位

<Outlet/>标签就是用来占位的,之后插入路由组件,类似于Vue中的RouterView

index 显示一级路由中的默认二级路由

当存在有多个自己路由时,当无法确定匹配哪一个自己路由,此时需要用到index,即默认匹配哪一个二级路由。

js 复制代码
 <Routes>
    <Route path='/film' element={<Film/>} >
        // 即当匹配到Film组件时,二级路由默认显示NowPlaying组件
        <Route index element={<Navigate to="/film/nowplaying"/>} />
        <Route path="nowplaying" element={<NowPlaying/>} />
        <Route path="comingsoon" element={<ComingSoon/>} />
    </Route>
</Routes>
声明式导航和编程式导航
  • 声明式导航:类似一个a标签,点击跳转(增加一个active的类名)

<link to="/center">个人中心<link/>

<NavLink to="/center">个人中心<NavLink/> 有高亮效果

  • 编程式导航:通过编写代码实现跳转

使用useNavigate(),返回一个函数用来实现编程式导航。 可以更改页面的URL,替代组件。

js 复制代码
let navigate = useNavigate()
// 通过注册点击事件中,实现页面跳转
<li key={item.id} onClick={()=>{ navigate('/center') }}>{ item.name }</li>
路由传参
  • (1)query方式,通过查询字符串的方式 /test?id=${id}&page=${page}

获取当前url中的查询字符串:useSearchParams(),得到一个数组

js 复制代码
// 数组中第一个元素:对象(查询参数的键值对)
// 数组中第二个元素:方法,修改查询参数
let [ searchParams , setSearchParams] = useSearchParams();
let id = searchParams.get('id'); // 通过get方法获取
  • (2)params方式,使用useParams() ,得到params对象

【注】:在params传参方式中,要先使用占位符进行占位!

js 复制代码
// 占位 /test/:id
const params = useParams()
// 得到的params就是一个对象
js 复制代码
import {BrowserRouter, HashRouter} from 'react-router-dom'

对应两种路由模式, 如果是BrowserRouter,会和后端进行一个沟通,先去后端查找,如果没有的话,才会在前端查找

路由懒加载

问题: 所有路由组件代码是打包在一块的, 打开首页就会加载, 但我们开始只需要看到首页路由的效果, 也就是只需要执行首页路由组件代码

解决: 对路由组件进行懒加载处理

深入理解 对路由组件进行拆分/单独打包 => import函数动态引入 访问路由时才去后台加载对应的打包文件 => lazy函数 指定loading界面 =>

首页加载速度过慢,采用路由懒加载方案

js 复制代码
import {lazy, Suspense} from 'react'

// 懒加载动态引入组件
const About = lazy(() => import('../pages/About'))

// 路由表
{
  element: <Suspense fallback={<div>正在加载中...</div>}>
    <About />
  </Suspense>
}

const lazyload = (path)=>{
    const Comp = lazy(()=>import(path))
    return (
        <Suspense>
            <Comp />
        </Suspense>
    )
}
useRoutes
相关推荐
星辰引路-Lefan5 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
飞鸟malred6 小时前
vite+tailwind封装组件库
前端·react.js·npm
TE-茶叶蛋6 小时前
Vue Fragment vs React Fragment
javascript·vue.js·react.js
xd0000210 小时前
12.vite,webpack构建工具
react.js
WildBlue10 小时前
🚀 React初体验:从“秃头程序员”到“数据魔法师”的奇幻漂流
前端·react.js
哼唧唧_11 小时前
使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作
react native·react.js·harmonyos·harmony os5·运动健康
_一两风12 小时前
React 组件化开发:从项目创建到组件通信
react.js
工呈士12 小时前
Context API 应用与局限性
前端·react.js·面试
钟看不钟用12 小时前
React(1)——渲染完整流程
react.js
胡gh12 小时前
深入理解React,了解React组件化,脱离”切图崽“,迈向高级前端开发师行列
前端·react.js