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
相关推荐
screct_demo9 小时前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
光头程序员17 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me17 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者17 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
VillanelleS20 小时前
React进阶之高阶组件HOC、react hooks、自定义hooks
前端·react.js·前端框架
傻小胖21 小时前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
flying robot1 天前
React的响应式
前端·javascript·react.js
GISer_Jing2 天前
React+AntDesign实现类似Chatgpt交互界面
前端·javascript·react.js·前端框架
智界工具库2 天前
【探索前端技术之 React Three.js—— 简单的人脸动捕与 3D 模型表情同步应用】
前端·javascript·react.js
我是前端小学生2 天前
我们应该在什么场景下使用 useMemo 和 useCallback ?
react.js