安装和创建项目
安装
安装react手脚架:
npm install create-react-app -g
创建项目:
create-react-app demo
进入文件夹并执行项目:
cd demo
npm start
创建基础目录
-
删除在
src
目录下所有文件 -
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> );
-
创建
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
-
创建
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>
}
注意点:
-
定义组件的时候,return 后面只能有一个根标签,不能有多个,但这个标签内部可以有其他多个标签
-
使用组件的时候,首字母必须大写
JSX语法糖
React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展,在React中会被babel编译为JavaScript。
JSX的特点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
JSX几个注意的格式:
-
React的JSX是使用大写和小写字母来区分本地组件和HTML组件
(如:html就用 div p h1 , 组件就用 MyButton App Home List等 )
-
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循环
- 调用变量,需要在return中直接使用单花括号-- **{}**调用
- 三目运算符直接在**{}**使用(做为 v-if 是否显示组件)
- 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的基本使用总结:
-
在构造函数定义状态数据
constructor(props){ super(props) this.state = { num: 20 } }
-
在jsx标签里使用状态数据
return ( <div> <p>{this.state.num}</p> </div> )
-
在方法里修改数据(注意只能使用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>
)
}
事件的使用
类组件
- 在类里定义方法 handleClick
- 在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>
)
}
}
函数组件
- 在函数里定义方法 handleClick
- 在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基本使用 总结:
-
在父组件中使用子组件的时候,可以给组件设定属性的值
<组件名 属性名=值 /> // 例如: <Header title="首页" bgc="green"/>
-
在定义子组件的时候,需要填入值的位置书写 this.props.属性名 来获取定义的值
<div style={{ height:40, backgroundColor:this.props.bgc, textAlign:"center", color:"#fff" }}> {this.props.title} </div>
-
在子组件内部可以定义默认值
class Header extends React.Component{ // 定义默认值 static defaultProps = { 属性名:默认值, bgc : "blue", title : "默认标题" } ... }
-
子元素的使用(了解):
// 在父组件中,以 <组件名 属性名=值></组件名> // 方式使用子组件的时候,可以添加子元素: <组件名 属性名=值>子元素</组件名> // 例如: <Header bgc="pink">Header子元素</Header> //在子组件内部,通过 this.props.children 来获取这个子元素 class Header extends React.Component{ ... return ( <div>显示子元素的地方:{this.props.children}</div> ) }
函数组件
-
在父组件中使用子组件的时候,可以给组件设定属性的值
<组件名 属性名=值 /> // 例如: <Header title="首页" bgc="green"/>
-
在定义子组件的时候,在组件函数的形参里填上 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> ) }
-
定义默认值(通过结构赋值来设置默认值)
import React, { useState } from "react" export default function Query({title:"lalal",bgc:"#fff"}) { return ( <div> <div style={{ backgroundColor: bgc, color:"#fff" }}> {title} </div> </div> ) }
-
子元素的使用
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 声明上下文数据类型 static childContextTypes = { 数据名: 数据类型 } // 1.2 向上下文控件中存值 getChildContext(){ return { 数据:值 } }
-
在“无论哪一级别”子组件中,只需声明需要的全局上下文数据,就可自动注入子组件的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。
实现步骤:
- 使用
createContext
创建Context对象 - 在顶层组件通过
provider
提供数据 - 在底层组件通过
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/>
密 码:<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/>
密 码:<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类:
-
不受控组件(和状态无关)
- input标签组件中,使用ref属性,来代表组件标识
- 组件渲染后,会自动把有ref属性的dom对象,挂到this.refs上
this.refs = {
ref名1 : dom对象
ref名2 : dom对象
} - 在需要的场景(一般指回调),使用this.refs来获取需要的对象,然后再做dom操作
-
受控组件(和状态紧密相关)
- 初始化状态
- 在input标签的value属性上,绑定状态(输入框的默认值和状态有关)
- 在input标签上维护onChange属性,触发时即时修改状态
- 自动:当状态修改,立即触发声明周期的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。
实现步骤:
- 使用
createContext
创建Context对象 - 在顶层组件通过
provider
提供数据 - 在底层组件通过
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元素对象或者是组件实例。(因为函数组件没有实例,所以这里的获取组件实例指的是获取类组件实例)
使用步骤:
- 导入useRef()函数
- 执行useRef()函数并传入null,返回值为一个对象,内部有一个current属性存放拿到的dom对象(组件实例)
- 通过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
时,展示某个商品的详情。
Route
的 index
属性就是用来展示默认子路由的。
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
使用步骤
-
创建reducer仓库
src/store/reducer.js
// 创建初始状态 const defaultState = { num: 1 } // 并导出一个函数 export default (state = defaultState) => { return state; }
-
创建redux入口文件
src/store/index.js
// 仓库的入口文件 // 引入reducer import reducer from './reducer'; // 创建仓库 import { createStore } from 'redux'; const store = createStore(reducer); // 导出仓库 export default store;
-
在
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 是提供器
-
在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事件的使用
-
在 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; }
-
在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)