React学习

341 阅读21分钟

React基础

英文官网 中文官网

JSX

  1. 全称:  JavaScript XML
  2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement(component,props,...children)方法的语法糖
  3. 作用: 用来简化创建虚拟DOM
    • 写法:var ele = <h1>Hello JSX!</h1>
    • 注意1:它不是字符串, 也不是HTML/XML标签
    • 注意2:它最终产生的就是一个JS对象
  4. 标签名任意: HTML标签或其它标签
  5. 标签属性任意: HTML标签属性或其它
  6. 基本语法规则
    • 遇到 <开头的代码, 以标签语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
    • 遇到以{开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
  7. babel.js的作用
    • 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
    • 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

函数组件和class组件

注意

  1. 组件名必须首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签

三大属性:props,state,refs

props

类式组件

 <script type="text/babel">
            //创建组件
            class Person extends React.Component {
                  //构造器是否接收props,是否传递super,取决于:是否希望在构造器中通过this访问props
                  constructor(props){
                        //如果不写,this.props为undefined
                        super(props);
                  }
                  static propTypes = {
                        name: PropTypes.string.isRequired,
                        sex: PropTypes.string,
                        age: PropTypes.number,
                  }
                  static defaultProps = {
                        sex: "未知",
                        age: "18",
                  }
                  render() {
                        // this Person实例对象
                        console.log(this);
                        //props是只读属性,不可以修改
                        // this.props.name = "34";错误
                        const { name, age, sex } = this.props
                        return (
                              <ul>
                                    <li>姓名:{name}</li>
                                    <li>年龄:{age}</li>
                                    <li>性别:{sex}</li>
                              </ul>
                        )
                  }
            }
            //渲染组件到页面
            ReactDOM.render(<Person name="tom" age={12} sex="女" speak={console.log(123)} />, document.getElementById('test0'))
      </script>

函数式组件

<script type="text/babel">
            // 函数式组件只能使用props
            function Person(props) {
                  const { name, sex, age } = props;
                  return (
                        <ul>
                              <li>姓名:{name}</li>
                              <li>年龄:{age}</li>
                              <li>性别:{sex}</li>
                        </ul>
                  )
            }
            //对prop的类型进行限制,类型,必要性限制
            Person.propTypes = {
                  name: PropTypes.string.isRequired,
                  sex: PropTypes.string,
                  age: PropTypes.number,
                  speak: PropTypes.func,//限制为函数
            }
            //指定标签的默认属性
            Person.defaultProps = {
                  sex: "未知",
                  age: "18",
            }
            ReactDOM.render(<Person name="haha" age={12} sex="女" />, document.getElementById("test"));
      </script>

props-types 和defaultProps

函数式组件

<script type="text/babel">
  function Person(props) {
    const { name, sex } = props;
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
      </ul>
    );
  }
  Person.propTypes = {
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number,
    speak: PropTypes.func,
  };
  Person.defaultProps = {
    sex: "女",
    age: 18,
  };
  ReactDOM.render(
    <Person name="tom" sex="男" />,
    document.getElementById("test")
  );
</script>

类式组件

class Person extends React.Component {
  static propTypes = {
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number,
    speak: PropTypes.func,
  };
  // 设置默认属性
  static defaultProps = {
    sex: "女",
    age: 18,
  };
  state = {};
  render() {
    const { name, sex, age } = this.props;
    // prop是只读的
    // this.props.name = "jack";
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }
}

state

注意

  1. 组件中render方法中的this为组件实例对象
  2. 组件自定义的方法中this为undefined,如何解决?
    • 强制绑定this: 通过函数对象的bind()
    • 箭头函数
  3. 状态数据,不能直接修改或更新

函数式组件————useState

import { useState } from "react";
import "./App.css";

export default function App() {
//初始化state
  const [todos, setTodos] = useState(() => {
    return [
      { id: "001", name: "吃饭", done: false },
      { id: "002", name: "睡觉", done: true },
      { id: "003", name: "打豆豆", done: false },
    ];
  });
  function addTodo(todoObj) {
    const newTodo = [todoObj, ...todos];
    //更新state
    setTodos(newTodo);
  }
  return (
    <div className="todo-container">
      <div className="todo-wrap">
        <Header addTodo={addTodo} />
        <List todos={todos} />
        <Footer />
      </div>
    </div>
  );
}

类式组件

import React, { Component } from "react";

export default class App extends Component {
  state={
    todos:[
      { id: "001", name: "吃饭", done: false },
      { id: "002", name: "睡觉", done: true },
      { id: "003", name: "打豆豆", done: false },
    ]
  }
  addTodo = (todoObj) => {
    const newTodo = [todoObj, ...this.state.todos];
    this.setState({todos: newTodo})
  };
  render() {
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header addTodo={this.addTodo} />
          <List todos={this.state.todos} />
          <Footer />
        </div>
      </div>
    );
  }
}

refs

使用形式

  1. 字符串形式的ref:<input ref="input1"/>
  2. 回调形式的ref:<input ref={(c)=>{this.input1 = c}}/>
  3. createRef创建ref容器myRef = React.createRef();<input ref={this.MyRef}/>

事件处理

注意

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件,而不是使用的原生DOM事件——为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)————为了的高效
  2. 通过event.target得到发生事件的DOM元素对象—--—不要过度使用ref
<script type="text/babel">
            class Demo extends React.Component {                  
                  myRef = React.createRef();
                  myRef1 = React.createRef();
                  showDataL = () => {
                        alert(this.myRef.current.value)
                  }
                  showDataR = () => {
                        alert(this.myRef1.current.value)
                  }
                  render() {
                        //html模板
                        //c表示当前节点 
                        return (
                              <div>
                                    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                                    <button onClick={this.showDataL}>点击提示左侧数据</button>
                                    <input ref={this.myRef1} onBlur={this.showDataR} type="text" placeholder="失去焦点提示数据" />
                              </div>
                        )
                  }
            }
            ReactDOM.render(<Demo />, document.getElementById("test"))
      </script>

生命周期

image.png image.png

虚拟DOM和DOM diffing算法

React 脚手架 Ajax

介绍

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
    • 包含了所有需要的配置(语法检查、jsx编译、devServer…)
    • 下载好了所有相关的依赖
    • 可以直接运行一个简单效果
  2. react提供了一个用于创建react项目的脚手架库: create-react-app
  3. 项目的整体技术架构为:  react + webpack + es6 + eslint
  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

创建项目

  1. 全局安装npm i -g create-react-app
  2. 切换到想创项目的目录,使用命令create-react-app xxx
  3. 进去项目文件夹cd xxx
  4. 启动项目npm start

脚手架目录分析

public ---- 静态资源文件夹

  • favicon.icon ------ 网站页签图标
  • index.html --------主页面
  • manifest.json ----- 应用加壳的配置文件
  • robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

  • App.css -------- App组件的样式
  • App.js --------- App组件
  • App.test.js ---- 用于给App做测试
  • index.css ------ 样式
  • index.js -------入口文件
  • logo.svg ------- logo图
  • reportWebVitals.js-------页面性能分析文件(需要web-vitals库的支持)
  • setupTests.js------ 组件单元测试的文件(需要jest-dom库的支持)

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <!-- %PUBLIC_URL%表示public下的路径 -->
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <!-- 开启理想视口,为了适配移动端 -->
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <!-- 用于指定网页添加到手机到手机主屏幕后的图标 -->
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
  <!-- 应用加壳时的配置文件,就是网页版转手机版的配置 -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>React App</title>
</head>

<body>
  <!-- 浏览器不支持js运行时显示 -->
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>

</html>

app.jsx

// 函数式组件
export default function App() {
  return (
    <h1>HEllO</h1> 
  );
}
//类式组件
// 属于分别暴露的引用,不是解构赋值
import {Component} from "react"
export default class App extends Component{
  render(){
    return(
      <h1>HEllO</h1>
    )
  }
}


index.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// concurrent 模式:终极模式
ReactDOM.createRoot(document.getElementById('root')).render(
    <App />
);
// legacy 模式:这个模式是当前React App使用的模式,但是可能不支持某些新特性
ReactDOM.render(<App/>,document.getElementById('root'))

TodoList案例

  1. nanoid()随机生成id :npm i nanoid
  2. 更新一个对象
<script>
    let obj = {a:1,b:2};
    let obj1 = {...obj,b:3};
    console.log(obj1);//a:1,b:3
</script>
  1. defaultChecked :只在页面的第一次渲染时有用

image.png

脚手架配置代理

方法一

在package.json中追加如下配置: "proxy":"http://localhost:5000" 缺点:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二

  1. 第一步:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

  1. 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
      	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}

存在问题
由于版本问题会出现无法访问的情况,上述配置是使用低版本时的配置
高版本需要改变配置为

const {createProxyMiddleware} = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    createProxyMiddleware("/api1", {
      //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
      	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    })
  );
};


说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

搜索案例

知识点

  • 连续解构赋值
const data = {a:{b:{c:1}}}
const data = {a:{b:1}}
const {a:{b:{c}}} = data;//联系解构赋值
console.log(c);//1
const {a:{b:sum}} = data;//连续解构赋值并对b进行重命名
console.log(sum);//1

消息的订阅与发布——PubSub.js

GitHub包地址 image.png

fetch发送请求

React路由

SPA应用

  1. 单页面应用(SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会做页面的局部刷新
  4. 数据都需要通过ajax请求获取,并在前端异步显示

路由理解

后端路由 path-function 前端路由 path-component

前端路由的实现原理

history 锚点跳转:

<a href="#demo1"></a>
<a href="#demo2"></a>
<a href="#demo3"></a>

路由的基本使用(react-router-dom5版本)

路由组件与一般组件的区别

  1. 写法不同: 一般组件:<Demo/> 路由组件: <Route path="/about" component={About}/>
  2. 存放位置不同: 一般路由:components 路由组件:pages
  3. 接收的props不同 一般组件: 传什么收什么 路由组件:接收三个固定参数
history:
 go:
 goBack:
 goForward:
 push:
 replace:
location:
 pathname:
 search:
 state:
match:
 params:
 path:
 url:

image.png

NavLink

activeClassName用于设置被点击时的class如果存在active的类名就不需要写activeClassName NavLink相比于Link有高亮效果

<NavLink
    activeClassName="bgc"
    className="list-group-item"
    to="/about"
>
    About
</NavLink>

二次封装NavLink

props可以接受到标签体中的内容,在this.props.children属性中
MyNavLink组件

export default class MyNavLink extends Component {
  render() {
    return (
      <NavLink activeClassName="bgc" className="list-group-item" {...this.props}/>
    );
  }
}

MyNavLink使用

<MyNavLink to='/about'>About</MyNavLink>

Switch组件

注册路由时使用Switch包裹,匹配成功之后不往下匹配

<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />

image.png

<Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Route path="/home" component={Test} />
</Switch>

image.png

多层样式丢失问题

目录结构 image.png 出错时的样式引入,index.html中

<link rel="stylesheet" href="./css/bootstrap.css">

出错时的路由链接

<div className="list-group">
   <MyNavLink to="/xxx/about">About</MyNavLink>
   <MyNavLink to="/xxx/Home">Home</MyNavLink>
</div>
<div className="col-xs-6">
   <div className="panel">
     <div className="panel-body">
        <Switch>
          <Route path="/xxx/about" component={About} />
          <Route path="/xxx/home" component={Home} />
        </Switch>
       </div>
   </div>
</div>

解决办法

  1. 改变引用路径<link rel="stylesheet" href="/css/bootstrap.css">
  2. 改变引用路径<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">(只适用react脚手架)
  3. 使用HashRouter包裹不使用BrowserRouter(一般不使用这种)

路由的模糊匹配和严格匹配

默认模糊匹配 Route匹配MyNavLink组件,一层一层一层匹配,最终效果不变

image.png 精准匹配,给Route加exact属性,有时候会导致二级路由不可使用

<Route exact={true} path="/xxx/about" component={About} />
//或
<Route exact path="/xxx/about" component={About} />

Redirect组件

Redirect写在所有Route组件的最下方

<Switch>
  <Route path="/xxx/about" component={About} />
  <Route path="/xxx/home" component={Home} />
  <Redirect to='/home'/>
</Switch>

二级路由(多级路由)

基本使用

  1. 注册子路由时要写上父路由的path值
  2. 路由匹配是按照注册路由的顺序进行的
<ul className="nav nav-tabs">
    <li>
        <MyNavLink to="/home/news" className="list-group-item ">
            News
        </MyNavLink>
    </li>
    <li>
        <MyNavLink to="/home/messages" className="list-group-item ">
            Message
        </MyNavLink>
    </li>
</ul>
<ul>
    <Switch>
    <Route path="/home/news" component={News}/>
    <Route path="/home/messages" component={Message}/>
    <Redirect path="/home/news"/>
    </Switch>
</ul>

向路由组件传递params参数

image.png 传递params

<div>
    <ul>
       {this.state.messages.map((message) => {
         return (
           <li key={message.id}>
             <Link
                  to={`/home/messages/detail/${message.id}/${message.title}`}
             >
             {message.title}
             </Link>
           </li>
         );
      })}
    </ul>
    <Route path="/home/messages/detail/:id/:title" component={Detail} />
</div>

接收params

const {id,title} = this.props.match.params

向路由组件传递search参数

image.png

  1. 路由链接(携带参数):<Link to={`/home/messages/detail?name=tom&age=18`}>xxx</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/home/messages/detail" component={Detail} />
  3. 接受参数:const {search} = this.props.location

注意
获取到的search是urlencoded编码字符串,需要借助querystring解析
querystring是react脚手架自带的一个库
urlencoded编码 : key=value&key = value

import qs from 'querystring
let obj = {name:'tom',age:18}
console.log(qs.stringify(obj));//name=tom&age=18
let str = name=tom&age=18;
console.log(qs.parse(str));//{name:'tom',age:18}

向路由组件传递state参数

image.png

  1. 路由链接(携带参数):<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>xxx</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Detail} />
  3. 接受参数:const {state} = this.props.location

注意
刷新也可以保留参数,因为使用的是BrowserRouter,但是如果清除浏览器缓存则state将变成undefined

push和replace

默认push replace的设置

<Link replace={true} to='path:'/demo/test'>xxx</Link>
//或
<Link replace to='path:'/demo/test'>xxx</Link>

编程式路由(this.props.history上的属性)

借助this.props.history对象上的API对操作路由跳转、前进、后退

  1. this.props.history.push('xxx'):xxx在传递params和search参数时直接写/组件中to的的值,在传递state参数时,xxx第一个参数写路径,第二个参数携带的state参数this.props.history.push('/home/message/detail',{id,title})
  2. this.props.history.replace('xxx'):xxx在传递params和search参数时直接写/组件中to的的值,在传递state参数时,xxx第一个参数写路径,第二个参数携带的state参数this.props.history.replace('/home/message/detail',{id,title})
  3. this.props.history.goBack()
  4. this.props.history.goForward()
  5. this.props.history.go(xxx): xxx写数值,正数前进,负数回退

image.png

withRouter组件

一个函数,加工一般组件,让一般组件拥有路由组件的API image.png

BrowserRouter和HashRouter的区别

  1. 底层原理不一样:
    BrowserRouter使用的是h5的historyAPI,不兼容IE9及以下版本
    HashRouter使用的是URL的哈希值
  2. path表现形式不一样
    BrowserRouter的路径中没有#
    HashRouter的路径中包含#
  3. 刷新后对路由state参数的影响 BrowserRouter没有任何影响,因为state保存在history对象中
    HashRouter刷新后会导致路由state1参数的丢失
  4. 备注:HashRouter可以解决一些路径错误相关的问题

redux

浅谈 React 状态管理工具

f7cc10938965931724bbbfcfefaaa4c.jpg

redux是什么

  1. redux是一个专门用于做状态管理的js库(不是react插件库)
  2. 它可以用在react,angular,vue等项目中,但基本与react配合使用
  3. 作用:集中式管理react应用中多个组件共享的状态

什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)
  2. 一个组件需要改变另一个组件的状态(通信)
  3. 总体原则:能不用就不用,如果不用比较吃力才考虑使用

redux工作原理

image.png

Action

  1. 动作的对象
  2. 包含2个属性
    • type:标识属性, 值为字符串, 唯一, 必要属性
    • data:数据属性, 值类型任意, 可选属性
  3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。

store

  1. 将state、action、reducer联系在一起的对象
  2. 如何得到此对象?
    • import {createStore} from 'redux'
    • import reducer from './reducers'
    • const store = createStore(reducer)
  3. 此对象的功能?
    • getState(): 得到state
    • dispatch(action): 分发action, 触发reducer调用, 产生新的state
    • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

求和案例

异步编程问题

  1. redux默认是不能进行异步处理的,
  2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
    使用异步中间件npm install --save redux-thunk

监听状态变化

  1. 在子组件中写
componentDidMount(){
    //检测redux中状态的变化,只要变化就调用render()
    store.subscribe(()=>{
      //假装更新,实则调用一个render()
      this.setState({})
    })
  }
  1. index.js中写
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import store from "./redux/store";
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
store.subscribe(() => {
  ReactDOM.createRoot(document.getElementById("root")).render(<App />);
});

核心API

  1. createstore() 作用:创建包含指定reducer的store对象
  2. store对象
    • 作用: redux库最核心的管理对象
    • 它内部维护着:state,reducer
    • 核心方法:
      • getState()
      • dispatch(action)
      • subscribe(listener)
    • 具体编码:
      • store.getState()
      • store.dispatch({type:'INCREMENT', number})
      • store.subscribe(render)
  3. applyMiddleware() 作用:应用上基于redux的中间件(插件库)
  4. combineReducers() 作用:合并多个reducer函数

react-redux

image.png

两类组件

  1. UI组件
  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 通过props接收数据(一般数据和函数)
  • 不使用任何 Redux 的 API
  • 一般保存在components文件夹下
  1. 容器组件
  • 负责管理数据和业务逻辑,不负责UI的呈现
  • 使用 Redux 的 API
  • 一般保存在containers文件夹下

相关API

  • Provider:让所有组件都可以得到state数据
<Provider store={store}>
    <App />
  </Provider>
  • connect:用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
  connect(
    mapStateToprops,
    mapDispatchToProps
  )(Counter)
  • mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
  return {
    value: state
  }
}
  • mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
    一个容器组件
//引入UI组件
import CountUI from '../../components/Count'
//引入连接UI组件的redux
import { connect } from 'react-redux'
import {createIncrementAction} from '../../redux/count_action'
/*
1.mapStateToProps函数返回的是一个对象
2.返回的对象中的key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
      return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象
2.返回的对象中的key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递方法
*/
function mapDispatchToProps(dispatch){
      return {
        jia: number => dispatch(createIncrementAction(number)),
      };
}
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

简化版本

//引入UI组件
import CountUI from "../../components/Count";
//引入连接UI组件的redux
import { connect } from "react-redux";
import { createIncrementAction } from "../../redux/count_action";
export default connect((state) => ({ count: state }), {
  jia: createIncrementAction,
})(CountUI);

优化版本

//引入连接UI组件的redux
import { connect } from "react-redux";
import { createIncrementAction } from "../../redux/count_action";

import React, { Component } from 'react'

class CountUI extends Component {
  increment = ()=>{
    const {value} = this.selectNumber
    this.props.jia(value*1);
  }
  decrement = ()=>{
    const { value } = this.selectNumber;
  }
  incrementAsync = ()=>{
    const {value} = this.selectNumber
  }
  render() {
    return (
      <div>
        <h1>当前数据为 {this.props.count}</h1>
        <select ref={(c) => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        &nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button>奇数时+</button>&nbsp;
        <button onClick={this.incrementAsync}>异步+</button>&nbsp;
      </div>
    );
  }
}

export default connect((state) => ({ count: state }), {
  jia: createIncrementAction,
})(CountUI);

纯函数和高阶函数

纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 必须遵守以下一些约束
    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

高阶函数

扩展内容

setState

  1. setState(stateChange, [callback])------对象式的setState
    • stateChange为状态改变对象(该对象可以体现出状态的更改)
    • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
  2. setState(updater, [callback])------函数式的setState
    • updater为返回stateChange对象的函数。
    • updater可以接收到state和props。
    • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
  3. 总结:
  • 对象式的setState是函数式的setState的简写方式(语法糖)
  • 使用原则:
    • 如果新状态不依赖于原状态 ===> 使用对象方式
    • 如果新状态依赖于原状态 ===> 使用函数方式
    • 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取

2. lazyLoad

路由组件的lazyLoad

	//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
	const Login = lazy(()=>import('@/pages/Login'))
	
	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
        //3.<Suspense fallback={<Loading/>}>,Loading组件不能使用懒加载
	<Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
        </Suspense>

3. Hooks

1. React Hook/Hooks是什么?

(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3. State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

4. Effect Hook

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
(3). 语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
(4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
    	componentWillUnmount() 

5. Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

4. Fragment

使用

	<Fragment><Fragment>
	<></>

作用

可以不用必须有一个真实的DOM根标签了

5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

1) 创建Context容器对象:
	const XxxContext = React.createContext()  	
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
	<xxxContext.Provider value={数据}>
		子组件
    </xxxContext.Provider>
3) 后代组件读取数据:
	//第一种方式:仅适用于类组件 
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据
	  
	//第二种方式: 函数组件与类组件都可以
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

注意

在应用开发中一般不用context, 一般都用它的封装react插件

6. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意: 
	只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
	不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

7. render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}
componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

9. 组件通信方式总结

组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式:

	1.props:
		(1).children props
		(2).render props
	2.消息订阅-发布:
		pubs-sub、event等等
	3.集中式管理:
		redux、dva等等
	4.conText:
		生产者-消费者模式

比较好的搭配方式:

	父子组件:props
	兄弟组件:消息订阅-发布、集中式管理
	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
            

Router6

Components

1.<BrowserRouter/>

  1. 作用:用于包裹整个应用
  2. 示例:
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { BrowserRouter } from 'react-router-dom';
    ReactDOM.render(
        <BrowserRouter>
            {/*整体结构(通常为App组件)*/}
        </BrowserRouter>
    )

2.<HashRouter/>

  1. 说明:作用与<BrowserRouter/>一样,但<HashRouter/>修改的是地址栏的hash值
  2. 备注:6.x版本中<BrowserRouter/><HashRouter/>的用法与5.x相同

3.<Routes/><Route/>

  1. v6版本中移出了先前的<Switch/>,引入了新的代替者:<Routes/>
  2. <Routes/><Route/>要配合使用,且必须要用<Routes/>包裹<Route/>
  3. <Route/>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件
  4. <Route caseSensitive/>属性用于指定:匹配时是否区分大小写(默认为false)
  5. 当URL发生变化时,<Routes/>都会查看其所有的Route元素以找到最佳匹配并呈现组件
  6. <Route/>也可以嵌套使用,且可以配合useRoutes()配置“路由表”,但需要通过<Outlet/>组件来渲染其子路由
  7. 示例代码
<Routes>
      {/* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */}
      <Route path="/login" element={<Login/>} />
      {/* 用于定义嵌套路由,home是一级路由,对应的路径/home */}
      <Route>
            {/* test1和test2是二级路由,对应的路径是/home/test1或/home/test2 */}
            <Route path="test1" element={<Test1/>} />
            <Route path="test2" element={<Test2/>} />
      </Route>
      {/* Route也可以不写element属性,这时就是用于展示嵌套路由,所对应的路径/users/xxx */}
      <Route path='users'>
            <Route path='xxx' element={<Demo/>}/>
      </Route>
</Routes>

5.<NavLink/>

  1. 作用:与<Link/>组件类似,且可以实现导航高亮的效果
  2. 示例:
// 注意:NavLink默认类名是active,下面是指定自定义的class
      //自定义样式
    <NavLink to="login" className={({isActive})=>{
      console.log('home',isActive);
      return isActive ? 'base one' : 'base'
    }}>
      login
    </NavLink>
/*
 默认情况下,当Home的子组件匹配成功,Home的导航也会高亮
 当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果
*/
<NavLink to='home' end>home</NavLink>

6.<Navigate/>

  1. 作用:只要<Navigate/>组件被渲染,就会修改路径,切换视图
  2. replace属性用于控制跳转模式(push或replace,默认push)
  3. 示例:
    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'

    export default function App() {
    const [sum,setSum] = useState(1);
    return (
    <div>
      <h3>HOME</h3>
      {/* 根据sum的值决定是否切换视图 */}
      {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to='/about' replace/>}
    </div>
      )
    }

7.<Outlet/>

  1. <Route/>产生嵌套时,渲染其对应的后续子路由

Hooks

  1. useRoutes():根据路由表,动态创建<Routes/><Route/>
  2. useNavigate():返回一个函数用来实现编程式导航
  3. useParams():回当前匹配路由的params参数,类似于5.x中的match.params
  4. useSearchParams():用于读取和修改当前位置的URL中的查询字符串,返回一个包含两个值的数组,内容分别为:当前的search参数,更新search的函数
  5. useLocation():获取当前location信息,对标5.x中的路由组件的location属性
  6. useMatch():返回当前匹配信息,对标5.x中路由租价的match属性

问题

ReactDOM.render()和React.createRoot().render()

// concurrent 模式:终极模式
ReactDOM.createRoot(document.getElementById('root')).render(
    <App />
);
// legacy 模式:这个模式是当前React App使用的模式,但是可能不支持某些新特性
ReactDOM.render(<App/>,document.getElementById('root'))

Error:React limits the number of renders to prevent an infinite loop.

原因:recat限制渲染次数,以防止无限渲染和渲染次数过多

function handleMouseLeave(flag) {
    setMouse(flag);
};

解决:

function handleMouseLeave(flag) {
    return () => setMouse(flag);
  };

Error: React Hook "useState" is called in function "index" that is neither a React function component nor a custom React Hook function

原因:在不是react的函数中调用ReactHook"useState"

image.png 解决:

image.png

A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.

react-router-dom的版本问题
6版本需要包<Routes>

redux中引入createStore有删除线