react 简单入门

安装和创建项目

安装

安装react手脚架:

npm install create-react-app -g

创建项目:

create-react-app demo

进入文件夹并执行项目:

cd demo
npm start

创建基础目录

  1. 删除在src目录下所有文件

  2. src目录下新建index.js文件作为入口文件

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
  3. 创建App.js作为入口组件

    //1、导入React核心模块
    import React from 'react'
    
    //2、定义组件类
    class Hello extends React.Component{   //类
        render(){     //函数
            return (   //返回值
                <div>
                    hello world !!! 我是组件222
                </div>
            )
        }
    }
    
    
    //3、导出组件
    export default Hello
    
  4. 创建pages文件夹放置页面组件,创建assets文件夹放置图片等静态文件,创建components文件夹放置公共组件

安装插件和库

使用scss

安装sass-loader和node-sass就直接可以创建scss文件直接使用

npm install sass-loader node-sass -S

采用 Normalize.css 初始化样式

安装

npm install Normalize.css

安装成功后,在src/pages下的index.js中:

//重置样式
import 'normalize.css'

安装react-router和创建路由文件

// 安装
npm install react-router-dom

// 创建src/router文件夹
mkdir router
cd router
mkdir index.js

路由文件 index.js

import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"

import Home from "../pages/home";
import Query from "../pages/query";
import Details from "../pages/details";
import Item from "../pages/item";

export default function Router() {
  return (
    <BrowserRouter>
      {/* 使用 Routes 替换曾经的 Switch */}
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/query' element={<Query />} />
        <Route path='/details' element={<Details />} >
          <Route path='item/:id' element={<Item />} />
        </Route>
        {/* 重定向到首页 */}
        <Route path="*" element={<Navigate to="/" />} />
      </Routes>
    </BrowserRouter>
  );
}

src/index.js文件修改为

import React from 'react';
import ReactDOM from 'react-dom/client';
import Router from "./router"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Router />
  </React.StrictMode>
);

安装react-redux

npm install react-redux
mkdir store

react 18基础语法

组件化开发

简单创建组件

组件开发分为两种类组件开发和函数组件开发

类组件开发简单模板

//1、导入React核心模块
import React from 'react'

//2、定义组件类
class Hello extends React.Component{   //类
    render(){     //函数
        return (   //返回值
            <div>
                hello world !!! 我是组件222
            </div>
        )
    }
}

//3、导出组件
export default Hello

函数组件开发简单模板

//  写法一
export default const Hello = (props) => {      
    return <div>{props.message}</div>
}

 //  写法二
export default const Hello = props => <div>{props.message}</div>  

// 写法三
export default function Hello(props) {
    return <div>{props.message}</div>
}

注意点:

  1. 定义组件的时候,return 后面只能有一个根标签,不能有多个,但这个标签内部可以有其他多个标签

  2. 使用组件的时候,首字母必须大写

JSX语法糖

React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展,在React中会被babel编译为JavaScript。

JSX的特点

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。

JSX几个注意的格式:

  1. React的JSX是使用大写和小写字母来区分本地组件和HTML组件

    (如:html就用 div p h1 , 组件就用 MyButton App Home List等 )

  2. JSX和html的标签属性的区别

    HTML标签属性 JSX 原因
    for htmlFor for在JS中为for循环关键字
    class className class在JS中为声明类关键字
    style 需使用JS对象(使用双花括号–{{}})
    <div>
        <p  style={{backgroundColor: "#ccc"}}>
            hello world !!! 我是组件2223
        </p>
    
        <img src={MyImg} alt="" className="img1" /> <br/>
        <label htmlFor="username">用户名:
            <input type="text" id="username"/>
        </label>
    
    </div>
    

JSX变量引用、三目运算符、for循环

  1. 调用变量,需要在return中直接使用单花括号-- **{}**调用
  2. 三目运算符直接在**{}**使用(做为 v-if 是否显示组件)
  3. for循环使用数组名.map(函数)(做为 v-for 循环显示组件)
import React, { Component } from 'react'

let name = "小明", num1=20, num2=30, arr=[1, 2, 3, 4, 5]

export default class App3 extends Component {
    render() {
        return (
            <div>
                {/* 这是注释的格式 */}
                {/* JSX中引用变量需要加单花括号 */}
                <p>{name}</p>
               
                {/* 三目运算符的使用 */}
                <p>num1和num2中比较大的是:{num1>num2? num1: num2}</p>


                {/* for循环的使用 */}
                <ul>
                    {/* 数组名.map(函数) */}
                    {
                        //格式1:
                        arr.map((v,k)=>(
                                <li key={k}>{v}</li>
                            )
                        )
                    }
                    {
                        //格式2:可以把上面的大括号和return换成小括号
                        arr.map((v,k)=>{    
                                return <li key={k}>{v}</li>
                            }
                        )
                    }
                </ul>
                
            </div>
        )
    }
}

组件状态 state 的基本使用

类组件

组件状态this.state的基本使用总结:

  1. 在构造函数定义状态数据

    constructor(props){
        super(props)
    
        this.state = {
            num: 20
        }
    }
    
  2. 在jsx标签里使用状态数据

    return (
        <div>
            <p>{this.state.num}</p>
        </div>
    )
    
  3. 在方法里修改数据(注意只能使用this.setState修改数据,不像vue,react不会监听数据)

    // 1、通过事件或者定时器触发:
        <button onClick={this.handleClick.bind(this)}>点击增加1</button>
    
    // 2、在事件函数中:
    this.setState({
        num: this.state.num+1
    })
    

函数组件

import React, { useState } from "react"

export default function Query() {
  // 1.定义状态
  const [num, setNum] = useState(0);

    
  const add = function () {
    // 3.修改数据
    setNum(num + 1)
  }

  return (
    <div>
      <h1>Query</h1>
      <span>2.使用状态数据:num的值:{num}</span>
      <button onClick={add}></button>
    </div>
  )
}

事件的使用

类组件

  1. 在类里定义方法 handleClick
  2. 在jsx 的标签属性里使用 οnclick={this.handleClick} 调用
import React, { Component } from 'react'

// 1、实现点击弹框效果(事件基本格式)
export default class App4 extends Component {
    handleClick(e){  
        alert(132456)
    }

    render() {
    	return (
            <div>
                <button onClick={this.handleClick}>按钮</button>
            </div>
        )
    }
}

// 

注意:方法中想使用组件的属性和方法(this调用),一定要使用.bind(this)改变this的指向,不然this指向的是这个方法,不是这个组件

实现双向数据绑定

export default class App4 extends Component {
    constructor(props){
        super(props)

        this.state = {
            str1: "123"
        }
    }
    
    handleChange(e){
        console.log(e)
        console.log(e.target)
        this.setState({
            str1: e.target.value
        })
    }
    
    render() {
        return (
            <div>
                <input type="text" onChange={this.handleChange.bind(this)}/>
                <p>{this.state.str1}</p>
            </div>
        )
    }
}

函数组件

  1. 在函数里定义方法 handleClick
  2. 在jsx 的标签属性里使用 οnclick={handleClick} 调用

不用考虑this了

import React, { useState } from "react"

export default function Query() {
    const handleClick= function(e){  
        alert(132456)
    }
    
    return (
        <div>
            <button onClick={handleClick}>按钮</button>
        </div>
    )
}

实现双向数据绑定

import React, { useState } from "react"

export default function Query() {

    const [num, setNum] = useState(0);
    
    const handleChange= function(e){  
        setNum(e.target.value)
    }
    
    return (
        <div>
            <input type="text" onChange={handleChange}/>
            <p>{num}</p>
        </div>
    )
}

组件属性 props 的基本使用

props 的作用就是接受父组件传输过来的值

类组件

props基本使用 总结:

  1. 在父组件中使用子组件的时候,可以给组件设定属性的值

    <组件名 属性名=/>  
    
    // 例如:
    <Header title="首页" bgc="green"/>
    
  2. 在定义子组件的时候,需要填入值的位置书写 this.props.属性名 来获取定义的值

    <div style={{
            height:40,
            backgroundColor:this.props.bgc,
            textAlign:"center",
            color:"#fff"
        }}>
        {this.props.title}
    </div>
    
  3. 在子组件内部可以定义默认值

    class Header extends React.Component{
        // 定义默认值
        static defaultProps = {
            属性名:默认值,
            bgc : "blue",
            title : "默认标题"
        }
        
        ...
    }
    
  4. 子元素的使用(了解):

    // 在父组件中,以
        <组件名 属性名=></组件名>
    // 方式使用子组件的时候,可以添加子元素:
        <组件名 属性名=>子元素</组件名> 
    
    // 例如:
    <Header bgc="pink">Header子元素</Header>
    
    //在子组件内部,通过  this.props.children  来获取这个子元素
    
    class Header extends React.Component{
    	
        ...
        
        return (
        	<div>显示子元素的地方:{this.props.children}</div>
        )
    }
    

函数组件

  1. 在父组件中使用子组件的时候,可以给组件设定属性的值

    <组件名 属性名=/>  
    // 例如:
    <Header title="首页" bgc="green"/>
    
  2. 在定义子组件的时候,在组件函数的形参里填上 props ,以 props.属性名 来获取定义的值

    import React, { useState } from "react"
    
    export default function Query(props) {
        
        return (
            <div>
                <div style={{
                        backgroundColor: props.bgc,
                        color:"#fff"
                    }}>
                    {props.title}
                </div>
            </div>
        )
    }
    
  3. 定义默认值(通过结构赋值来设置默认值)

    import React, { useState } from "react"
    
    export default function Query({title:"lalal",bgc:"#fff"}) {
        return (
            <div>
                <div style={{
                        backgroundColor: bgc,
                        color:"#fff"
                    }}>
                    {title}
                </div>
            </div>
        )
    }
    
  4. 子元素的使用

    import React, { useState } from "react"
    
    export default function Query(props) {
        return (
            <div>显示子元素的地方:{props.children}</div>
        )
    }
    

限制传进来的props属性的数据类型

安装prop-types模块

npm install --save prop-types

类组件

import React, { Component } from 'react'
import PropTypes from 'prop-types'

// 子组件
class Header extends Component {

    // propTypes名字是固定的,PropTypes.类型 是定义类型
    static propTypes = {
        title: PropTypes.string
    }
    
    render() {
        return (
            <div style={{height:40, backgroundColor:'blue', textAlign:"center", color: "#fff"}}>
                {this.props.title}
            </div>
        )
    }
}


// 父组件
export default class App extends Component {
    render() {
        return (
            <div>
                <Header title="首页" />
            </div>
        )
    }
}

函数组件

import React, { useState } from "react"
import PropTypes from 'prop-types'

// 子组件
export default function List(props) {
  return (
    <div>
      <div>{props.name}</div>
      <div>{props.num}</div>
    </div>
  )
}

// 类型限制
List.propTypes = {
  name: PropTypes.string,
  num: PropTypes.number
}

// 默认值
List.defaultProps = {
  name: "没值",
  num: 0
}

// 父组件
<List name="刘秀辉" num={111}></List>

使用context进行props属性值的多级传递

类组件

React 组件之间的通信是基于 props 的数据传递,数据需要一级一级从上到下传递。如果组件级别过多传递就会过于麻烦。React中Context配置可以解决组件跨级值传递。

实现步骤:

  1. 在父组件中,声明数据类型和值

    // 1.1 声明上下文数据类型
    static childContextTypes = {
        数据名: 数据类型
    }
    
    // 1.2 向上下文控件中存值
    getChildContext(){
        return {
            数据:值
        }
    }
    
  2. 在“无论哪一级别”子组件中,只需声明需要的全局上下文数据,就可自动注入子组件的context属性中

    static contextTypes = {
        数据名: 数据类型
    }
    
    // 在子组件中使用:
    {this.context.con}
    

具体代码:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

// 父组件
export default class App extends Component {

    static childContextTypes = {
        con: PropTypes.string
    }

    getChildContext(){
        return {
            con: "爷爷组件"
        }
    }

    render() {
        return (
            <div>
                <Child/>
            </div>
        )
    }
}


// 子组件
class Child extends Component {
    render() {
        return (
            <div>
                <ChildChild/>
            </div>
        )
    }
}


// 孙组件
class ChildChild extends Component {

    static contextTypes = {
        con: PropTypes.string
    }
    render() {
        return (
            <div>
                子组件中的子组件————{this.context.con}
            </div>
        )
    }
}

函数组件

useContext()用于在组件之间共享状态,而不必显式地通过组件树的逐层传递 props。

实现步骤:

  1. 使用createContext创建Context对象
  2. 在顶层组件通过provider提供数据
  3. 在底层组件通过useContext函数获取数据
// 1. 创建一个context.js
import { createContext } from 'react';
export const ageContext = createContext(null); //创建了自己的ageContext

// 2. 在父级包裹想要传递数据的所有组件:
import React, { useState } from "react"
import { AgeContext } from "./context"
import List from "./list"

export default function Home() {
  const [age, setAge] = useState(11);

  return (
    <div>
      <h1>我是父级</h1>
      <div>
        <AgeContext.Provider value={{ setAge, age }}>
          <List name="刘秀辉" num={111}></List>
        </AgeContext.Provider>
      </div>
    </div>
  )
}

// 3.子级组件调用孙子级组件
import React, { useState } from "react"
import Child from "./child"

export default function List(props) {
  return (
    <div>
      <h2>我是子级</h2>
      <Child></Child>
    </div>
  )
}

// 4. 孙子级组件使用变量
import React, { useContext } from "react"
import { AgeContext } from "./context"

export default function Child() {
  const { age } = useContext(AgeContext)
  return (
    <div>
      我是孙子,父级传过来的值为:{age}
    </div>
  )
}

React生命周期

类组件

生命周期:就是指一个对象的生老病死。 React的生命周期指从组件被创建到销毁的过程。掌握了组件的生命周期,就可以在适当的时候去做一些事情。

React生命周期可以分成三个阶段:

1、实例化(挂载阶段):对象创建到完全渲染

2、存在期(更新期):组件状态的改变

3、销毁/清除期:组件使用完毕后,或者不需要存在与页面中,那么将组件移除,执行销毁。

1.实例化/挂载阶段

  • constructor():

  • componentWillMount():组件将要挂载

  • render():

  • componentDidMount():组件已经挂载

export default class App3 extends Component {
    // 生命周期第一个阶段: 挂载/初始化阶段
    constructor(props){
        console.log("1.1 constructor: 构造初始化")
        // 调用父类构造方法
        super(props)

        // 初始化状态数据
        this.state = {

        }
        // 事件函数this的绑定
        this.handleClick = this.handleClick.bind(this)

    }

    UNSAFE_componentWillMount(){
        console.log("1.2 componentWillMount")
        //做一些准备性的工作,比如提示正在加载
    }

    componentDidMount() {
        console.log("1.4 componentDidMount")
        //异步加载数据
    }
    

    handleClick(){
        alert("点击了p标签")
    }

    render() {
        console.log("1.3 render")
        return (
            <div>
                <p onClick={this.handleClick}>这是一个展示生命周期的组件</p>
                <p onClick={this.handleClick}>这是一个展示生命周期的组件</p>
                <p onClick={this.handleClick}>这是一个展示生命周期的组件</p>
            </div>
        )
    }
}

2.存在期/更新期

存在期:且件已经渲染好并且用户可以与它进行交互。通常是通过一次鼠标点击、手指点按者键盘事件来触发一个事件处理器。随着用户改变了组件或者整个应用的state,便会有新的state流入组件树,并且我们将会获得操控它的机会。

  • componentWillReceiveProps():组件将接收父级传过来的数据

  • shouldComponentUpdate():应更新组件

  • componentWillUpdate():组件将更新

  • render()

  • componentDidUpdate():组件已经更新

在上面的组建中继续书写生命周期函数:

render() {
    console.log("1.3/2.4 render")
    return (
        <div>
            <p onClick={this.handleClick}>这是一个展示生命周期的组件</p>
            <p onClick={this.handleClick}>这是一个展示生命周期的组件</p>
            <p onClick={this.handleClick}>这是一个展示生命周期的组件{this.state.num}</p>
        </div>
    )
}

handleClick(){
    this.setState({
        num:22
    })
}

// 生命周期第二个阶段  存在期/更新期
componentWillReceiveProps(){
    console.log("2.1 componentWillReceiveProps")
}

shouldComponentUpdate(nextProps, nextState) {
    console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")
    console.log("旧的值:", this.state.num)
    console.log("新的值:", nextState.num)

    // return true   则执行render
    // return false   则不执行render
    //这里返回值是布尔值,但不应该写死,
    //而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
    return this.state.num !== nextState.num

}

componentWillUpdate(nextProps, nextState) {
    console.log("2.3 componentWillUpdate 更新前的生命周期回调")
}

componentDidUpdate(prevProps, prevState) {
    console.log("2.5 componentDidUpdate 更新后的生命周期回调")
}

以上执行的是组件内部state数据更新前后的生命周期函数,

其实,对于组件的props属性值发生改变的时候,同样需要更新视图,执行render

componentWillReceiveProps() 这个方法是将要接收新的props值的时候执行,而props属性值从父组件而来,所以需要定义父组件:

class App3 extends Component {
    ...
    
	//生命周期第二个阶段  存在期/更新期
    UNSAFE_componentWillReceiveProps(nextProps){
        console.log("2.1 componentWillReceiveProps 这个方法props属性值更新的时候才会执行,更新state数据则不会执行这个方法")
        
        console.log(nextProps)
    }
	...
    shouldComponentUpdate(nextProps, nextState) {
        console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")

        // return true   则执行render
        // return false   则不执行render
        //这里返回值是布尔值,但不应该写死,
        //而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
        return (this.state.num !== nextState.num || this.props.fatherNum !== nextProps.fatherNum)  //不仅仅是state数据跟新时候需要执行render,props属性的值更新的时候也要执行render,所以要多加一个判断条件

    }
}

export default class Father extends Component{
    constructor(props){
        // 调用父类构造方法
        super(props)

        // 初始化状态数据
        this.state = {
            fatherNum:0
        }
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({
                fatherNum:10
            })
        }, 2000);
    }
    
    render(){
        return(
            <App3 fatherNum={this.state.fatherNum}/>
        )
    }
}

3.销毁期

componentWillUnmount() 销毁组件前做一些回收的操作

componentDidMount() {
    console.log("1.4 componentDidMount")
    console.log("------------------------------------------")

    //异步加载数据
    document.addEventListener("click", this.closeMenu);
}
closeMenu(){
    console.log("click事件 closeMenu")
}

// 生命周期第三个阶段  卸载期/销毁期
componentWillUnmount() {
    console.log("3.1 componentWillUnmount 做一些回收的操作")
    document.removeEventListener("click", this.closeMenu);
}

生命周期小结

React组件的生命周期
3大阶段10个方法
1、初始化期(执行1次)
2、存在期 (执行N次)
3、销毁期 (执行1次)

小结:
componentDidMount : 发送ajax异步请求

​ shouldComponentUpdate : 判断props或者state是否改变,目的:优化更新性能

函数组件(使用useEffect模拟生命周期)

首先我们知道在React中的函数式组件是没有 生命周期 的,那我们想在函数式组件中实现class组件中生命周期钩子函数的效果应该怎么操作呢?

调用函数组件就直接执行render了

模拟componentDidMount

useEffect 依赖 [ ]

React.useEffect(() => {
	console.log("这是模拟componentDidMount钩子函数")
},[])
//第二个参数一定是一个空数组,因为如果不写会默认监听所有状态,这样写就不会监听任何状态,只在初始化时执行一次。

模拟componentDidUpdate

useEffect 无依赖 ,或者 依赖 [a,b,c]

//在此之前需要使用useRef这个hooks
const flag = React.useRef(null)
React.useEffect(() => {
	if(!flag.current){
		flag.current = true
	} else {
		console.log("更新了")
	}
})

模拟componentWillUnmount

useEffect 中返回一个函数

React.useEffect(() => {
	// console.log("这是模拟componentDidMount钩子函数")
	return () => {
		console.log("这是模拟componentWillUnmount钩子函数")
	}
},[])

不受控组件和受控组件

类组件

1.不受控组件

组件中表单元素没有写value值,与state数据无关,不受组件管理。(不推荐)

//不受控组件
import React, { Component } from 'react'

export default class App4 extends Component {

        
    handleClick(){
        console.log(this.refs.username.value)
        console.log(this.refs.password.value)
    }
    render() {
        return (
            <div>
                {/* 这种组件和组件本身state数据没有关系,所以不受组件管理,称为不受控组件(无约束组件)*/}
                {/* 这种写法的每个表单元素的验证在登录按钮的点击事件完成,用户体验很差 */}
                用户名:<input type="text" ref="username"/> <br/> <br/>&emsp;码:<input type="text" ref="password"/><br/> <br/>
                <button onClick={this.handleClick.bind(this)}>登录</button>
            </div>
        )
    }
}

2.受控组件

组件中表单元素的value值受组件的state数据控制,并且该表单元素有onChange事件,我们可以在事件中对该表单做实时验证,验证值是否合法然后做相应的操作(推荐)

//受控组件
import React, { Component } from 'react'

export default class App4 extends Component {

    constructor(props){
        // 调用父类构造方法
        super(props)

        // 初始化状态数据
        this.state = {
            value:"admin"
        }
        // 事件函数this的绑定
        this.handleChange = this.handleChange.bind(this)

    }

    handleChange(e){
        console.log(e.target.value)
        //可以在这个方法内部做一些实时验证,验证值是否合法做响应的操作
        this.setState({
            value:e.target.value
        })
    }
    render() {
        return (
            <div>
               
                用户名:<input type="text" value={this.state.value} onChange={this.handleChange}/> <br/> <br/>&emsp;码:<input type="text" /><br/> <br/>
                <button>登录</button>
            </div>
        )
    }
}

函数组件

1.不受控组件

import React, { useRef } from "react"

export default function Form(){
    const usernameRef = useRef(null)

    const handleClick=function(){
        console.log(usernameRef.current.value)
    }
    return(
        <div>
            <input type="text" ref="username"/>
            <button onClick={this.handleClick.bind(this)}>登录</button>
        </div>
    )
}

2.受控组件

import React, { useState } from "react"

export default function Form(){
    const [username,setUsername] = useState(null)

    const handleClick=function(){
        setUsername(usernameRef.current.value)
    }
    return(
        <div>
            <input type="text" value={username} onChange={this.handleChange}/>
        </div>
    )
}

小结

React中的表单组件,分为2类:

  1. 不受控组件(和状态无关)

    1. input标签组件中,使用ref属性,来代表组件标识
    2. 组件渲染后,会自动把有ref属性的dom对象,挂到this.refs上
      this.refs = {
      ref名1 : dom对象
      ref名2 : dom对象
      }
    3. 在需要的场景(一般指回调),使用this.refs来获取需要的对象,然后再做dom操作
  2. 受控组件(和状态紧密相关)

    1. 初始化状态
    2. 在input标签的value属性上,绑定状态(输入框的默认值和状态有关)
    3. 在input标签上维护onChange属性,触发时即时修改状态
    4. 自动:当状态修改,立即触发声明周期的render方法,显示最先的状态值

使用
如果对值的控制度不高,直接不受控组件
如果要严格把控值的操作,受控可以做表单验证等严格的逻辑(推荐)

react Hooks

首先:React的组件创建方式,一种是类组件,一种是纯函数组件。

React团队认为组件的最佳写法应该是函数,而不是类。

但是纯函数组件有着类组件不具备的特点:

  • 纯函数组件没有状态
  • 纯函数组件没有生命周期
  • 纯函数组件没有this

这就注定,纯函数组件只能做UI展示的功能,如果涉及到状态的管理与切换,我们就必须得用类组件或者redux,但是在简单的页面中使用类组件或者redux会使代码显得很重。

因此,React团队设计了React hooks(钩子)。

React Hooks的意思是:组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。

四种常用的钩子:

  • useState()
  • useReducer()
  • useContext()
  • useEffect()
  • useRef()

UseState()

我们知道,纯函数组件没有状态,useState()用于为函数组件引入状态。

举例:
state是一个普通变量:

//引入状态钩子useState()
import React,{useState} from 'react'
import './App.css';

function App() {
  //useState创造一个状态,赋值一个初始值,当前赋值的初始值为0
  //count是一个变量,此变量的值指向当前状态的值 相当于this.state
  //setcount是一个函数,此函数可以修改状态的值 相当于this.Setstate
  const [count,setCount] = useState(0)
  const addCount = ()=>{
    setCount(count+1)
  }
  return (
    <div className="App">
       <div>{count}</div>
       <button onClick = {addCount}>点击加1</button>
    </div>
  );
}

export default App;

state是一个对象:

setState()不会局部更新

意思是,如果state是一个对象,不能部分setState,所以我们使用…user将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖。

import React,{useState} from 'react'
import './App.css'

function App(){
  const [user,setUser]=useState({age:'11',name:'Bob'})
  const handerClick=()=>{
      setUser({
          ...user,
          name:'jack'
      })
  }
  return (
    <div className='App'>
        <h1>{user.name}</h1>
          <h1>{user.age}</h1>
          <button onClick={handerClick}>
            Click
          </button>
    </div>
  )
}
 
export default App;

useReducer()

useState() 的替代方案,用于包含多种状态,或者下一个 state 依赖于之前的 state,实现函数组件的状态管理。

基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。

举例:

点击加1,点击减1

//实现点击改变状态
import React,{useReducer} from 'react'
import './App.css';
function App(){
  
  //useReducer(),state表示状态,action表示相关操作
  const reducer = (state,action)=>{
    if (action.type === 'add') {
      return {
          ...state,
          count: state.count + 1,
      }
    }else if (action.type === 'jian') {
      return {
          ...state,
          count: state.count - 1,
      }
    } else {
      return state
    } 
  }
 
  const addCount=()=>{
    dispatch({
      type:'add'
    })
  }
  const min=()=>{
    dispatch({
      type:'jian'
    })
  }
  const [state,dispatch] = useReducer(reducer,{count:0})
  return(
    <div>
      <div>{state.count}</div>
      <button onClick={addCount}>点击加1</button>
      <button onClick={min}>点击减1</button>
    </div>
  )
}
export default App;

useContext()

useContext()用于在组件之间共享状态,而不必显式地通过组件树的逐层传递 props。

实现步骤:

  1. 使用createContext创建Context对象
  2. 在顶层组件通过provider提供数据
  3. 在底层组件通过useContext函数获取数据
//引入状态钩子useState()
import React,{useContext} from 'react'
import './App.css';
function App(){
    //通过createContext来创建上下文
    const AppContext = React.createContext()

    const Achild = ()=>{
        //在子组件中通过useContext来获取数据
        const {name1} = useContext(AppContext)
        return(
            <div>
                这是组件A,使用的name值是:{name1}
            </div>
        )
    }
    
    const Bchild = ()=>{
        //在子组件中通过useContext(Context句柄)来获取数据
        const {name2} = useContext(AppContext)
        return(
            <div>
                这是组件B,使用的name值是:{name2}
            </div>
        )
    }
    return (
            //AppContext.Provider数据共享组件,来确定共享范围,通过value来分发内容
          <AppContext.Provider value={{name1:'jack',name2:'Bob'}}>
              <Achild></Achild>
              <Bchild></Bchild>
          </AppContext.Provider>
        );
}
export default App;

useEffect()

useEffect()可以检测数据更新 。,可以用来更好的处理副作用,比如异步请求等。

useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect()的依赖项。

只要数组发生改变,useEffect()就会执行。

当第二项省略不填时,useEffect()会在每次组件渲染时执行,这一点类似于componentDidMount。

useEffect回调在dom渲染完毕之后执行 和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了

举例:

第二个参数省略时:

import React,{useState,useEffect} from 'react'
import './App.css';
function App(){
  const [loading,setLoading] = useState(true)
  //相当于componentDidMount
  //useEffect()第二个参数未填
  useEffect(()=>{
    setTimeout(()=>{
      setLoading(false)
    },3000)
  })
  //loadling为true时显示Loading... 3秒后loading变成了false,显示内容加载完毕
  return (
    loading?<div>Loading</div>:<div>内容加载完毕</div>
  )
}
export default App;

第二个参数存在时:
name改变时会触发useEffect()函数,

import React,{useState,useEffect} from 'react'
import './App.css';
function AsyncPage({name}){
    const [loading,setLoading] = useState()
    const [person,setPerson] = useState({})
    //useEffect()函数在组件初始化执行一次,之后,name改变时才会执行
    //组件渲染时,两秒后从Loading变为Bob
    //name改变时,先从Bob变为Loading,两秒后变为指定名字
    useEffect(()=>{
      setTimeout(()=>{
        setLoading(false)
        setPerson({name})
      },2000)
    },[name])
    return(
      loading?<div>Loading...</div>:<div>{person.name}</div>
    )
  }
  
function App(){
  const [name,setName] = useState('Bob')
  const changeName = (name)=>{
    setName(name)
  }
  return (
    <div>
      <AsyncPage name = {name}/>
      <button onClick = {()=>changeName('Jack')}>将名字改为jack</button>
      <button onClick = {()=>changeName('Tom')}>将名字改为Tom</button>
    </div>
  )
}
export default App;

useEffect()返回一个函数:

当useEffect()返回一个函数时,该函数会在组件卸载时执行。

举例:

当点击switch时,组件被卸载,定时器被清除,控制台不再打印。

import React,{useEffect,useState} from 'react'
import './App.css';

function Test (){
  useEffect(()=>{
    let timer = setInterval(()=>{
      console.log('定时器正在执行')
    },1000)
    return ()=>{
      //清除定时器
      clearInterval(timer)
    }
  },[])
  return(
    <div>this is test</div>
  )
}

function App(){
  const [flag,setFlag] = useState(true)
  return (
    <div>
      {flag?<Test/>:null}
      <button onClick={()=>{setFlag(false)}}>switch</button>
    </div>
  )
}

export default App;

useRef()

用于在函数组件中获取真实的DOM元素对象或者是组件实例。(因为函数组件没有实例,所以这里的获取组件实例指的是获取类组件实例)

使用步骤:

  1. 导入useRef()函数
  2. 执行useRef()函数并传入null,返回值为一个对象,内部有一个current属性存放拿到的dom对象(组件实例)
  3. 通过ref绑定要获取的元素或者组件实例。

举例:

获取dom和组件实例,可以看到结果在控制台打印了出来

import React,{useEffect, useRef} from 'react'
import './App.css';

//组件实例 类组件(函数组件没有实例)
//dom对象 标签

class Test extends React.Component{
  render(){
    return (
      <div>我是类组件</div>
    )
  }
}

function App(){
  const testRef = useRef(null)
  const h1Ref = useRef(null)
  //useEffect回调在dom渲染完毕之后执行
  //和vue里边的Watch效果比较像,但是执行时机是不同的 watch一开始就执行了
  useEffect(()=>{
    console.log(testRef.current)
    console.log(h1Ref.current)
  },[])
  return(
    <div>
      {/* 获取类组件实例 */}
      <Test ref={testRef}/>
      {/* 获取DOM对象 */}
      <h1 ref={h1Ref}>this is h1</h1>
    </div>
  )
}
export default App;

自定义钩子函数

根据自己的业务需求,自行封装一个钩子函数以供自己使用。

举例:自定义一个获取表单数据的钩子函数

import React,{useState} from 'react'
import './App.css';


// 自定义hook(use开头)
// 重用受控表单创建state和onChange方法逻辑
/**
 * 
 * @param {string | number} initialValue 初始默认值
 * @returns 
 */
//获取表单数据
const useInput = (initialValue) => {
  const [value, setValue] = useState(initialValue)

  return {
    value,
    onChange: e => setValue(e.target.value)
  }
}

// 表单组件
const  App = () => {

  const username = useInput('admin')

  const password = useInput('')

  const onSubmit = (e) => {
    //阻止默认事件发生
    e.preventDefault()
    // 获取表单值
    console.log(username.value, password.value);
  }

  return (
    <form onSubmit={onSubmit} >
      <input type="text" {...username} />
      <input type="password" {...password} />
      <button type="submit">提交</button>
    </form>
  );
}
export default App;

React Hooks中可以对性能进行优化的函数

useMemo()

具有缓存作用,有助于避免在每次渲染时都进行高开销的计算。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把创建函数和依赖项数组作为参数传入useMemo,当某个依赖改变时才会重新执行useMemo()函数。
如果没有提供依赖项数组,useMemo()每次渲染时都会重新执行useMemo()函数。

举例:
useMemo()监听count的值,当count的值改变时,newValue会更新。

import { useState, useMemo} from 'react';

export default () => {
    const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useMemo(()=>{
        console.log(`count 值为${count}`)
        console.log(`num 值为 ${num}`)
        return count+num
    },[count])
    return(
        <div>
            <h1>{count}</h1> 
            <button onClick={()=>{setCount(count+1)}}>count + 1</button>
            <hr/>
            <h1>{num}</h1> 
            <button onClick={()=>{setNum(num+1)}}>Num + 1</button>
            <hr/>
            <h2>{newValue}</h2>
        </div>
    )
}

点击5次num+1,num变为5,虽然newValue仍然为0,但是num=5已经被缓存了;点击count+1,他会计算count此时的值1与num缓存的值5的和,最终结果newValue为6。

useCallback()

useCallback 可以说是 useMemo 的语法糖,能用 useCallback 实现的,都可以使用 useMemo, 常用于react的性能优化

与useMemo()一样,依赖数组改变时才会重新执行useCallback()函数。

如果没有依赖数组,每次渲染都会重新执行useCallback()函数。

const memoizedCallback = useCallback(() => {doSomething(a, b)},[a, b]);

举例:

和上述useMemo()的效果一样,区别是useCallback()调用newValue时是:newValue()

import React,{ useState, useCallback} from 'react';

function App(){
  const  [count, setCount] = useState(0)
    const [num, setNum] = useState(0)
    const newValue = useCallback(()=>{
        console.log(`count 值为${count}`)
        console.log(`num 值为 ${num}`)
        return count+num;
    },[count])
    
    return(
        <div>
            <h1>{count}</h1> 
            <button onClick={()=>{setCount(count+1)}}>count + 1</button>
            <hr/>
            <h1>{num}</h1> 
            <button onClick={()=>{setNum(num+1)}}>Num + 1</button>
            <hr/>
            {/* 调用useCallback 返回的值 */}
            <h2>{newValue()}</h2>
        </div>
    )
}
export default App;

useMemo()和useCallback()的区别

  • useMemo()返回缓存的变量(memoized)
  • useCallback()返回缓存的函数

react-router 6

常用组件介绍

组件 作用
路由模式 BrowserRouter 约定模式 为 history,使用 HTML5 提供的 history API 来保持 UI 和 URL 的同步
路由模式 HashRouter 约定模式 为 hash,使用 URL 的 hash 来保持 UI 和URL 的同步
声明式跳转 NavLink 声明式跳转 还可以约定 路由激活状态
声明式跳转 Link 声明式跳转 无激活状态
重定向 Navigate 重定向 v5是 Redirect组件
匹配并展示 Route 匹配组件,并展示组件。即匹配成功后,组件立即被替换成匹配的组件
排他性匹配 Routes 排他性匹配。如果不想使用包容性,那么使用v5是Switch。
子路由占位组件 Outlet 配合NavLink组件显示子路由页面占位
高阶组件 withRouter 把不是通过路由切换过来的组件中,将 history、location、match 三个对象传入props对象上(高阶组件)

基础结构

import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"

import Home from "../pages/home";
import Query from "../pages/query";
import Details from "../pages/details";
import Item from "../pages/item";

export default function Router() {
  return (
    <BrowserRouter>
      {/* 使用 Routes 替换曾经的 Switch */}
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/query' element={<Query />} />
        <Route path='/details' element={<Details />} >
          <Route path='item/:id' element={<Item />} />
        </Route>
        {/* 重定向到首页 */}
        <Route path="*" element={<Navigate to="/" />} />
      </Routes>
    </BrowserRouter>
  );
}

路由组件

Outlet

配合 NavLink 就有tabs的效果

import React from "react"
import { Outlet, NavLink } from "react-router-dom"

export default function Details() {
  return (
    <div>
      <h1>Details</h1>
      <div>
        <NavLink to="/details/item/12">子节点</NavLink>
      </div>
      <Outlet />
    </div>
  )
}

withRouter

页面跳转

跳到另外的页面

Link组件

<Link to="/aaa">点击跳转页面</Link>

useNavigate 编程式跳转

import React from "react";
import { useNavigate } from "react-router-dom";

export default function Goods() {
  const navigate = useNavigate();

  const handleClickToHome = () => {
    navigate("/");

    // history 的 replace 模式
    // navigate("/", { replace: true });
      
    // navigate(-1); 返回上一页
      
    // navigate('/login',{state: '我从登陆页面过来了!!!'}) 带参数跳转
  };
  
  return (
    <div>
      <h2>Goods Page</h2>
      <button onClick={handleClickToHome}>to Home</button>
    </div>
  );
}

在本页面显示

NavLink组件

<NavLink to="/details/item/12">子节点</NavLink>

获取路由的参数

useParams 获取动态路由的值

// 上一个页面传值
navigate('/login/17');

// 本页面接收
import { useEffect } from "react";
import { useParams, useSearchParams } from "react-router-dom"

export default function Item() {
  const para = useParams();

  return (
    <div>
      <h1>Item</h1>
      <p>{para.id}</p>
    </div>
  )
}

useSearchParams 获取查询字符串的值

// 上一个页面传值
navigate('/login',{state: '我从登陆页面过来了!!!'})

// 本页面接收
import { useEffect } from "react";
import { useParams, useSearchParams } from "react-router-dom"

export default function Item() {
  const [searchParams, setSearchParams] = useSearchParams();

  useEffect(() => {
      
    setSearchParams({
      id: 2,
      name: "liuxiugui"
    })
      
  }, [])

  return (
    <div>
      <h1>Item</h1>
      <p>{searchParams.get("id")}</p>
      <p>{searchParams.get("name")}</p>
    </div>
  )
}

useLocation 获取上一个页面传递进来的 state 参数

// 上一个页面传值
navigate('/login?name=xiaoming&age=10')

// 本页面接收
import { useEffect } from "react";
import { useLocation } from "react-router-dom"
export default function Item() {
    
  const currentLocation = useLocation();
    
  return (
    <div>
      <h1>Item</h1>
      <p>{currentLocation.get("id")}</p>
    </div>
  )
}

useRoutes通过配置生成路由

useRoutes 可以将数组对象形式的路由,直接在页面上使用。

// 入口文件,src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./index";


ReactDOM.render((
  <BrowserRouter>
    <App />
  </BrowserRouter>
), document.getElementById("root"));

// src/router/routes.tsx
import React from "react";
import { RouteObject } from "react-router-dom";
import Home from "../home/Home";
import Goods from "../goods/Goods";
import Customer from "../customer/Customer";
import NotFound from "../not-found/NotFound";
import GoodsDetail from "../goods/goods-detail/GoodsDetail";
import GoodsList from "../goods/goods-list/GoodsList";

const routes: RouteObject[] = [
  {
    path: "/",
    element: <Home />,
    children: [
      {
        path: "/goods",
        element: <Goods />,
        children: [
          { index: true, element: <GoodsList /> },
          { path: ":id", element: <GoodsDetail /> }
        ]
      },
      {
        path: "/customer",
        element: <Customer />,
      },
      {
        path: "*",
        element: <NotFound />,
      },
    ]
  }
];

export default routes;

// src/App.tsx
import React from "react";
import { useRoutes } from "react-router-dom";
import routes from "./router/routes";

export default function App() {

  const element = useRoutes(routes);
  return (
    <>
      {element}
    </>
  );
}

默认子路由

当页面有多个子路由,比如在 /goods 时,页面展示 商品列表/goods/:id时,展示某个商品的详情。

Routeindex 属性就是用来展示默认子路由的。

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../home/Home";
import Goods from "../goods/Goods";
import Customer from "../customer/Customer";
import NotFound from "../not-found/NotFound";
import GoodsDetail from "../goods/goods-detail/GoodsDetail";
import GoodsList from "../goods/goods-list/GoodsList";

export default function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path='/' element={<Home />}>
          <Route path='goods' element={<Goods />} >
            {/* 默认 子路由 ,在页面 路由为 /goods ,会展示该子路由 */}
            <Route index element={<GoodsList />}/>

            <Route path=":id" element={<GoodsDetail />}/>
          </Route>

          <Route path='customer' element={<Customer />} ></Route>
          <Route path="*" element={<NotFound />} /> 
        </Route>

      </Routes>
    </BrowserRouter>
  );
}

redux 4 和 react-redux 8

使用步骤

  1. 创建reducer仓库src/store/reducer.js

    // 创建初始状态
    const defaultState = {
      num: 1
    }
    
    
    // 并导出一个函数
    export default (state = defaultState) => {
      return state;
    }
    
  2. 创建redux入口文件src/store/index.js

    // 仓库的入口文件
    
    // 引入reducer
    import reducer from './reducer';
    // 创建仓库
    import { createStore } from 'redux';
    
    const store = createStore(reducer);
    
    // 导出仓库
    export default store;
    
  3. src/index.js文件里使用react-redux的Provider组件调用数据

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import Router from "./router"
    import { Provider } from "react-redux"
    import store from './store';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <Provider store={store}>
          <Router />
        </Provider>
      </React.StrictMode>
    );
    
    // Provider 是提供器
    
  4. 在ui组件里

    import React from "react"
    import { connect } from "react-redux"
    
    function Reduxtest(props) {
      return (
        <div>
          <h1>数字为:{props.num}</h1>
        </div>
      )
    }
    
    // 状态映射:将reduler中的state映射成props,让开发者可以在组件中使用props.num去调用state中的num
    const mapStateToProps = (state) => {
      return {
        num: state.num
      }
    }
    
    // connect是连接器
    // connect(state映射,dispatch的映射)(当前组件名称)
    export default connect(mapStateToProps)(Reduxtest)
    

action事件的使用

  1. 在 reducer仓库src/store/reducer.js 增加 action 事件操作

    // 创建初始状态
    const defaultState = {
      num: 1
    }
    
    
    // 并导出一个函数
    export default (state = defaultState, action) => {
    
      // 解决地址未发生改变,ui不刷新问题
      let newState = JSON.parse(JSON.stringify(state))
      if (action.type == "addNum") {
        newState.num += action.value;
      }
    
      // 推荐用 switch() 选择值
    
      return newState;
    }
    
  2. 在ui组件增加事件派发组件

    import React from "react"
    import { connect } from "react-redux"
    
    function Reduxtest(props) {
      return (
        <div>
          <h1>数字为:{props.num}</h1>
          <button onClick={() => { props.addNum() }} >累加</button>
        </div>
      )
    }
    
    const mapStateToProps = (state) => {
      return {
        num: state.num
      }
    }
    
    // 事件派发映射:将reducer中的事件映射成props,让开发者可以在组件中使用props.addNum()去实现num的累加
    const mapDispatchToProps = (dispatch) => {
      return {
        addNum() {
          const action = {
            type: "addNum",
            value: 2
          }
          dispatch(action)
        }
      }
    }
    
    // connect(state映射,dispatch的映射)(当前组件名称)
    export default connect(mapStateToProps, mapDispatchToProps)(Reduxtest)
    

你可能感兴趣的:(#,react,react.js,javascript,前端)