react 简单入门

这篇博客详细介绍了React的入门知识,包括如何安装和创建项目,组件化开发,JSX语法,组件状态管理和事件处理,以及React Router和Redux的基础用法。文章涵盖了React 18的基础语法,如组件创建、状态管理和生命周期,还讲解了如何使用scss、Normalize.css以及react-router和react-redux。此外,还讨论了不受控和受控组件的概念,以及React Hooks的使用,如useState、useReducer、useContext和useEffect。
摘要由CSDN通过智能技术生成

安装和创建项目

安装

安装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原因
    forhtmlForfor在JS中为for循环关键字
    classclassNameclass在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>
        )
    }
}

// <button onClick={this.handleClick.bind(this)}>按钮</button>

注意:方法中想使用组件的属性和方法(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)
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值