React基础大全
文章目录
- 一、React基本介绍
- 1.虚拟DOM优化
- 1.1 原生JS渲染页面
- 1.2 React渲染页面
- 2.需要提前掌握的JS知识
- 二、入门
- 1.React基本使用
- 2.创建DOM的两种方式
- 2.1 使用js创建(一般不用)
- 2.2 使用jsx创建
- 3.React JSX
- 3.1 JSX常见语法规则
- 3.2 for循环渲染数据
- 4.模块与组件、模块化与组件化
- 4.1 模块
- 4.2 组件
- 4.3 模块化
- 4.4 组件化
- 三、面向组件编程
- 1.安装React开发者调试工具
- 2.自定义组件
- 2.1 函数式组件
- 2.2 类式组件
- 3.组件实例(class)三大核心属性
- 3.1 状态-state
- 3.1.1 基础使用(不适用)
- 3.1.2 代码优化
- 3.2 属性-props
- 3.2.1 基础用法
- 3.2.2 校验props属性值
- 3.2.3 函数式组件实现
- 3.3 refs和事件处理
- 3.3.1 字符串形式(不推荐)
- 3.3.2 回调函数形式
- 3.3.3 refs容器形式
- 3.3.4 事件处理
- 4.收集表单数据
- 4.1 非受控组件案例
- 4.2 受控组件案例
- 4.3 函数柯里化
- 4.4 不使用函数柯里化实现方式
- 5.组件的生命周期
- 5.1 生命周期-案例
- 5.2 生命周期(旧)
- 5.2.1 流程图
- 5.2.2 示例代码
- 5.3 生命周期(新)
- 5.3.1 流程图
- 5.3.2 代码案例
- 5.3.2 getSnapshotBeforeUpdate-案例
- 5.4 DOM的diffing算法
- 四、react应用(基于react脚手架)
- 1.创建react应用
- 1.1 react脚手架
- 1.2 创建项目并启动
- 1.3 项目结构
- 2.组件目录定义和导入
- 3.样式模块化
- 4.vscode安装react插件
- 5.组件化编码流程
- 6.组件组合使用-TodoList案例
- 6.1 拆组件
- 6.2 依赖安装
- 6.3 总结
- 五、react-axios
- 1.安装axios
- 2.代理配置
- 2.1 方法一(不使用)
- 2.2 方法二(常用)
- 3.github搜索案例
- 六、react-router
- 1.相关理解
- 1.1 SPA理解
- 1.2 路由的理解
- 1.3 react-router-dom的理解
- 2.react-router-dom
- 2.1 安装
- 2.2 路由组件使用
- 2.3 路由组件和一般组件的区别
- 2.4 NavLink
- 2.5 封装NavLink组件
- 2.6 Switch使用
- 2.7 样式丢失
- 2.8 路由模糊匹配
- 2.9 Redirect重定向
- 2.10 嵌套(多级)路由
- 2.11 向路由组件传递参数数据
- 2.11.1 传递params参数
- 2.11.2 传递search参数
- 2.11.3 传递state参数
- 2.12 路由跳转的两个模式
- 2.12.1 push模式
- 2.12.2 replace模式
- 2.13 编程式路由导航
- 2.14 让一般组件也有路由组件的参数-withRouter
- 2.15 BrowserRouter与HashRouter的区别
- 七、组件库-Ant Design
- 1.安装
- 2.入门体验
- 3.按需引入样式
- 4.自定义主题
- 4.1 安装包
- 4.2 修改package.json
- 4.3 创建配置文件
- 八、redux
- 1.理解
- 1.1 redux是什么
- 1.2 使用场景
- 2.redux的核心API
- 2.1 安装redux
- 2.2 redux工作流程
- 2.3 使用案例
- 2.3.1 求和案例-纯react版本
- 2.3.2 求和案例-redux精简版
- 2.3.3 求和案例-redux完整版
- 2.4 异步action
- 2.4.1 安装
- 2.4.2 注册中间件
- 2.4.3 异步aciton定义
- 九、react-redux
- 1.模型图
- 2.安装
- 3.相关概念
- 4.基础操作(单组件)
- 4.1 index.js
- 4.2 redux相关
- 4.3 容器
- 4.4 UI组件
- 5.基础操作(代码优化)
- 5.1 容器优化
- 5.2 redux状态变化机制
- 5.3 容器传入store优化-Provider
- 5.4 整合UI组件和容器组件
- 6.基础操作(多组件)数据共享
- 6.1 实现效果
- 6.2 redux目录结构
- 6.3 相关代码
- 7.纯函数
- 7.1 reducer注意事项
- 7.2 纯函数
- 8.redux开发者工具
- 8.1 安装扩展程序
- 8.2 安装库
- 8.3 store.js注册插件
- 十、react打包
- 十一、react扩展
- 1.setState
- 1.1 对象式
- 1.2 函数式
- 2.路由懒加载(lazyLoad)
- 2.1 无懒加载
- 2.2 懒加载
- 3.Hooks
- 3.1 是什么?
- 3.2 三个常用的Hook
- 3.3 State Hook
- 3.4 Effect Hook
- 3.5 Ref Hook
- 4.Fragment
- 5.Context
- 5.1 使用
- 5.2 类组件代码示例
- 5.3 函数组件代码示例
- 6.组件优化
- 6.1 解决办法1
- 6.2 解决办法2(推荐)
- 7.render props
- 7.1 如何向组件内部动态传入带内容的结构(标签)
- 7.2 children props
- 7.3 render props
- 8.错误边界
- 9.组件通信方式总结
- 十二、React Router6
- 1.概述
- 2.component
- 2.1 BrowerRouter
- 2.2 HashRouter
- 2.3 Routes与Route
- 2.4 Link
- 2.5 NavLink
- 2.6 Navigate
- 2.7 Outlet
- 3.Hooks
- 3.1 useRoutes()
- 3.2 useNavigate()
- 3.3 useParams()
- 3.4 useSearchParams()
- 3.5 useLocation()
- 3.6 useMatch()
- 3.7 useInRouterContext()
- 3.8 useNavigationType()
- 3.9 useOutlet()
- 3.10 useResolvedPath()
- 4.综合案例
- 4.1 useRouters路由表
- 4.2 嵌套路由
- 4.3 路由传参
- 4.4 编程式路由导航
一、React基本介绍
- React是用于构建用户界面的JavaScript的库,将数据渲染为HTML视图的开源库。
- 三部曲:
- 1.发送请求获取数据
- 2.处理数据(过滤、整理格式等)
- 3.操作DOM呈现页面
- 为什么要学?
- 1.原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)。
- 2.原生JavaScript直接操作DOM,浏览器会进行大量的
重绘重排
- 3.原生JavaScript没有组件化编码方案,代码复用率低。
- React特点:
- 1.采用组件化模式、声明式编码,提高开发效率及组件复用率。
- 2.在React Native中可以使用React语法进行移动端开发。
- 3.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。
- 官网
- 英文官网:https://react.dev/
- 中文官网:
- https://zh-hans.react.dev/
- https://react.docschina.org
1.虚拟DOM优化
1.1 原生JS渲染页面
1.2 React渲染页面
2.需要提前掌握的JS知识
- 判断this的指向
- class(类)
- ES6语法规范
- npm包管理器
- 原型、原型链
- 数组常用方法
- 模块化
二、入门
- 相关react练习的js,在本文的资源绑定里面
1.React基本使用
- babel的作用:
- 1.将ES6的语法转化为ES5
- 2.将JSX语法转为JS
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello_react</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建虚拟DOM(一定不要写引号,不需要写引号)const VDOM = <h1>Hello, React</h1>// 2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'));</script>
</body>
</html>
2.创建DOM的两种方式
- 为什么React使用JSX语法而不是JS
- JSX写法更适用于复杂的页面渲染场景
- 关于虚拟DOM:
- 1.本质是Object类型的对象(一般对象)
- 2.虚拟DOM绑定的函数很少,真实DOM比较多,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
- 3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
2.1 使用js创建(一般不用)
...省略<!-- 创建一个容器 --><div id="test"></div><script type="text/babel">// 1.创建虚拟DOMconst VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span', {}, 'Hello,React'))// 2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'));</script>
...省略
2.2 使用jsx创建
...省略<!-- 创建一个容器 --><div id="test"></div><script type="text/babel">// 1.创建虚拟DOM(一定不要写引号,不需要写引号)const VDOM = <h1 id="title"><span>Hello, React</span></h1>// 2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'));</script>
...省略
3.React JSX
- 全称:JavaScript XML
- react定义的一种类似于XML的JS扩展语法:JS +XML↵
- 本质是React.createElement(component,props,.….children)方法的语法糖
- 作用:用来简化创建虚拟DOM操作
- 写法:
var ele = <h1>Hello JSX</hw>
- 注意:它不是字符串,也不是HTML/XML标签,最终产生的是一个JS对象
- 写法:
3.1 JSX常见语法规则
- JS语法规则:
- 1.定义虚拟DOM时,不要写引号
- 2.标签中混入JS表达式时要用{}
- 表达式就是能被变量接收的:var a = 表达式;
- 表达式和代码的区别
- 表达式:
- a
- a+b
- demo(1)
- arr.map()
- function test () {}
- 代码:
- if(){}
- for(){}
- switch(){case:xxxx}
- 表达式:
- 3.样式的类名指定不要用class,要用className。
- 4.内联样式,要用style={{key:value}}的形式去写。
- 如果是需要定义font-size属性,那么需要改成驼峰命名:fontSize
- 5.虚拟DOM里面只能有一个根标签
- 6.标签必须闭合:
- 例如input标签:两种写法
<input type="text"/>
<input type="text"/></input>
- 例如input标签:两种写法
- 7.标签首字母
- (1)若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
- (2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello_react</title><style>.title{background-color:blueviolet;}</style>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">const a = "A";const b = "B";// 1.创建虚拟DOM(一定不要写引号,不需要写引号)const VDOM = (<div><h1 className="title"><span style={{color: 'white', fontSize: '29px'}}>{a.toLocaleLowerCase()}</span></h1><h1><span style={{color: 'blue', fontSize: '29px'}}>{b.toLocaleLowerCase()}</span></h1></div>)// 2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'));</script>
</body>
</html>
3.2 for循环渲染数据
- 由于React的虚拟DOM的原因,如果遍历数据,需要有个key,DOM优化算法就是根据key进行判断是否存在。
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">const title = "前端js框架列表";const framework_list = [{"value": 1, "label": "Angular"}, {"value": 2, "label": "React"}, {"value": 3, "label": "Vue"}, ];// 1.创建虚拟DOM(一定不要写引号,不需要写引号)const VDOM = (<div><h1 className="title"><span>{title}</span></h1><ul>{framework_list.map((item)=>{return <li key={item.value}>{item.label}</li>})}</ul></div>)// 2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'));</script>
</body>
4.模块与组件、模块化与组件化
4.1 模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件↵
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用 js,简化js的编写,提高js 运行效率↵
4.2 组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么:一个界面的功能更复杂
- 作用:复用编码,简化项目编码,提高运行效率
4.3 模块化
- 当应用的js都以模块来编写的,这个应用就是一个模块化的应
4.4 组件化
- 当应用是以多组件的方式实现,这个应用就是一个组件化的应用
三、面向组件编程
1.安装React开发者调试工具
- 需要使用chrome浏览器
2.自定义组件
2.1 函数式组件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>hello_react</title><style>.title{background-color:blueviolet;}</style>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建函数式组件function Demo(){// 组件里面的this,会经过babel,但是babel会开启严格模式,不允许this指向window,所以会打印undefinedconsole.log(this)return <h2>我是组件</h2>}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))/*执行了ReactDOM.render(<Demo/>...…………之后,发生了什么?1.React解析组件标签,找到了Demo组件。2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。*/</script>
</body>
</html>
2.2 类式组件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>类组件</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class MyComponent extends React.Component {render(){//render是放在哪里的?—MyComponent的原型对象上,供实例使用。//render中的this是谁?-MyComponent的实例对象。return <h2>我是类定义的组件</h2>}}// 2.渲染组件到页面ReactDOM.render(<MyComponent/>, document.getElementById('test'))/*执行了ReactDOM.render(<MyComponent/>...…………之后,发生了什么?1.React解析组件标签,找到了MyComponent组件。2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。*/</script>
</body>
</html>
3.组件实例(class)三大核心属性
3.1 状态-state
- 理解:状态其实就是数据,驱动页面的显示内容
- state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
- 组件被称为"状态机",通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
注意事项:
1.组件中 render方法中的this为组件实例对象组件自定义的方法中this为undefined,如何解决?
a.强制绑定this:通过函数对象的bind()
b.箭头函数
2.状态的数据不可以直接更新,需要使用setState进行更改,React才会同步更新到页面显示。
3.更新是一种合并,不是替换。只会替换setState传入的key和value,没有传入的,不会进行删除。
3.1.1 基础使用(不适用)
- 添加按钮绑定事件流程:
- 1.定义一个类组件
- 2.编写render方法
- 3.编写点击事件函数
- 4.在构造器中绑定点击函数
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>类组件</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Weather extends React.Component {// 构造器只会调用一次,调用几次组件,才会调用几次。constructor(props){super(props)// 初始化状态this.state = {isHot: true, wind: "微风"}//解决changeWeather中this指向问题this.changeWeather = this.changeWeather.bind(this)}// render调几次? 1+n次 1是初始化的那次 n是状态更新的次数render(){// 读取状态const {isHot, wind} = this.statereturn <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>}// changeWeather调用几次?——点几次调几次changeWeather(){//changeweather放在哪里? Weather的原型对象上,供实例使用//由于changeWeather是作为onclick的回调,所以不是通过实例调用的,是直接调用//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined// 解决办法:this.changeWeather = this.changeWeather.bind(this)//console.log(this)// 注意:状态(state)不可直接更改,下面这行就是直接更改!!!// this.state.isHot = ! this.state.isHot// 注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。this.setState({isHot: ! this.state.isHot})}}// 2.渲染组件到页面ReactDOM.render(<Weather/>, document.getElementById('test'))</script>
</body>
</html>
3.1.2 代码优化
- 优化内容:
- 1.避免在构造器中,写大量的绑定代码:需要改成箭头函数,箭头函数会寻找外层的this
- 2.避免在构造器中,赋值state:而是直接在类里面命名
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>类组件</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Weather extends React.Component {state = {isHot: true, wind: "微风"}render(){const {isHot, wind} = this.statereturn <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>}//自定义方法——要用赋值语句的形式+箭头函数changeWeather = ()=>{this.setState({isHot: ! this.state.isHot})}}// 2.渲染组件到页面ReactDOM.render(<Weather/>, document.getElementById('test'))</script>
</body>
</html>
3.2 属性-props
- 作用:通过标签属性从组件外向组件内传递变化的数据
- 注意:
- 1.props是只读的,无法修改
- 2.函数式组件有props,但是没有state和refs,如果需要实现,需要用到新特性hooks
3.2.1 基础用法
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>props基本使用</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Person extends React.Component {render(){const {name,sex,age} = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}// 2.渲染组件到页面ReactDOM.render(<Person name="tom" sex="女" age="18"/>, document.getElementById('test'))// 下面使用解包进行传递,但是ES语法是不允许是解包一个对象的,下面是因为babel识别到了React标签用了这个语法,特殊进行了转换,才可以使用const p = {name:"tom", sex:"女", age:"18"}ReactDOM.render(<Person {...p}/>, document.getElementById('test'))</script>
</body>
</html>
3.2.2 校验props属性值
- 从React16开始,需要引入prop-types.js才可以使用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>props基本使用</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Person extends React.Component {// 属性值校验static propTypes = {// name必传的字符串类型name: PropTypes.string.isRequired,// sex非必填的字符串类型sex: PropTypes.string,// 非必传的数字类型age: PropTypes.number,// 传入一个函数speak: PropTypes.func}// 属性值不传的默认值static defaultProps = {sex: "不知道男女",age: 18}render(){const {name,sex,age} = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}// 2.渲染组件到页面const p = {"name": "tom", sex:"女", age:18, speak: speak}ReactDOM.render(<Person {...p}/>, document.getElementById('test'))function speak () {console.log("说话")}</script>
</body>
</html>
3.2.3 函数式组件实现
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>props基本使用</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建函数式组件function Person (props) {const {name,sex,age} = propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}// 属性值校验Person.propTypes = {// name必传的字符串类型name: PropTypes.string.isRequired,// sex非必填的字符串类型sex: PropTypes.string,// 非必传的数字类型age: PropTypes.number,}// 属性值不传的默认值Person.defaultProps = {sex: "不知道男女",age: 18}// 2.渲染组件到页面const p = {"name": "tom", sex:"女", age:18}ReactDOM.render(<Person {...p}/>, document.getElementById('test'))</script>
</body>
</html>
3.3 refs和事件处理
- refs:组件内的标签可以定义 ref 属性来标识自己,类似组件的里面的id
- 平时开发只需要使用内联函数形式即可
- 注意:ref不要过度使用(发生事件的DOM元素和操作的DOM元素,这个时候就可以不写ref,而是使用事件处理)
3.3.1 字符串形式(不推荐)
- 官方不推荐,因为存在效率问题
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {showData = ()=>{const {input1} = this.refsalert(input1.value)}render(){const {name,sex,age} = this.propsreturn (<div><input type="text" ref="input1" placeholder="点击按钮提示数据"/><button onClick={this.showData}>点我提示左侧的数据</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
3.3.2 回调函数形式
更新数据的时候,重新render会调用两次(常用)*
- 如果ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成class的绑定函数的方式可以避免上述问题,
但是大多数情况下它是无关紧要的
。 - 下面是内联函数的写法,更新的时候会调用两次
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {showData = ()=>{console.log(this)alert(this.input1.value)this.setState({})}render(){const {name,sex,age} = this.propsreturn (<div><input type="text" ref={(currentNode) =>{this.input1 = currentNode;console.log("@@", currentNode);}} placeholder="点击按钮提示数据"/><button onClick={this.showData}>点我提示左侧的数据</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
通过将 ref 的回调函数定义成class的绑定函数的方式可以避免上述问题(不常用)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {showData = ()=>{console.log(this)alert(this.input1.value)this.setState({})}saveInput = (currentNode)=>{this.input1 = currentNode}render(){const {name,sex,age} = this.propsreturn (<div><input type="text" ref={this.saveInput} placeholder="点击按钮提示数据"/><button onClick={this.showData}>点我提示左侧的数据</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
3.3.3 refs容器形式
- React.createRef
- 该容器只能存储一个ref,而且current是当前节点
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {/*React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点*/myRef = React.createRef()showData = ()=>{alert(this.myRef.current.value)}render(){const {name,sex,age} = this.propsreturn (<div><input type="text" ref={this.myRef} placeholder="点击按钮提示数据"/><button onClick={this.showData}>点我提示左侧的数据</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
3.3.4 事件处理
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {/*(1).通过onXxx属性指定事件处理函数(注意大小写)a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件 --- 为了更好的兼容性b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) --- 为了的高效(2)通过event.target得到发生事件的DOM元素对象*/myRef = React.createRef()showData = (event)=>{alert(event.target.value)}render(){const {name,sex,age} = this.propsreturn (<div><input onBlur={this.showData} type="text" placeholder="失去焦点提示数据"/></div>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
4.收集表单数据
- 表单的组件分类:
- 受控组件:好处是可以省略ref
- 非受控组件:现用现取
4.1 非受控组件案例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {handleSubmit = (event)=>{// 阻止表单提交event.preventDefault()const {username, password} = thisalert(`你的用户名是:${username.value},你的密码是:${password.value}`)}render(){return (<form action="http://127.0.0.1" onSubmit={this.handleSubmit}>用户名:<input ref={c => this.username = c} type="text" name="username"/>密码:<input ref={c => this.password = c} type="password" name="password"/><button>登录</button></form>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
4.2 受控组件案例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Demo extends React.Component {//初始化状态,表单信息state = {username: '', //用户名password: '' // 密码}// 保存用户名到状态中saveUsername = (event)=>{this.setState({username: event.target.value})}// 保存密码到状态中savePassword = (event)=>{this.setState({password: event.target.value})}handleSubmit = (event)=>{// 阻止表单提交event.preventDefault()const {username, password} = this.statealert(`你的用户名是:${username},你的密码是:${password}`)}render(){return (<form action="http://127.0.0.1" onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveUsername} type="text" name="username"/>密码:<input onChange={this.savePassword} type="password" name="password"/><button>登录</button></form>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
4.3 函数柯里化
- 解决的问题:由于4.2中,每个字段都需要写一个onChange函数,会造成很多的重复工作。
- 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
- 常见的高阶函数有:Promise、setTimeout、arr.map()等等
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>函数柯里化</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">/*高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。*/// 1.创建类式组件class Demo extends React.Component {//初始化状态,表单信息state = {username: '', //用户名password: '' // 密码}// 保存表单数据到状态中saveFormData = (datType)=>{return (event)=>{this.setState({[datType]: event.target.value})}}handleSubmit = (event)=>{// 阻止表单提交event.preventDefault()const {username, password} = this.statealert(`你的用户名是:${username},你的密码是:${password}`)}render(){return (<form action="http://127.0.0.1" onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>密码:<input onChange={this.saveFormData('password')} type="password" name="password"/><button>登录</button></form>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
4.4 不使用函数柯里化实现方式
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ref和事件处理</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">/*高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。*/// 1.创建类式组件class Demo extends React.Component {//初始化状态,表单信息state = {username: '', //用户名password: '' // 密码}// 保存表单数据到状态中saveFormData = (datType, event)=>{this.setState({[datType]: event.target.value})}handleSubmit = (event)=>{// 阻止表单提交event.preventDefault()const {username, password} = this.statealert(`你的用户名是:${username},你的密码是:${password}`)}render(){return (<form action="http://127.0.0.1" onSubmit={this.handleSubmit}>用户名:<input onChange={(event) => {this.saveFormData('username', event)}} type="text" name="username"/>密码:<input onChange={(event) => {this.saveFormData('password', event)}} type="password" name="password"/><button>登录</button></form>)}}// 2.渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('test'))</script>
</body>
</html>
5.组件的生命周期
- 组件从创建到死亡它会经历一些特定的阶段。
- React 组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数,中做特定的工作。
5.1 生命周期-案例
- 需求:
- 1.标题需要2秒中从透明度1渐变到0再到1
- 2.点击按钮,组件消失
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>组件生命周期</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Life extends React.Component {state = {opacity: 1}death = () => {// 卸载组件ReactDOM.unmountComponentAtNode(document.getElementById('test'))}// 组件被挂载之后调用componentDidMount(){this.timer = setInterval(() => {// 获取原状态let {opacity} = this.state// 减小0.1opacity -= 0.1if(opacity <= 0) opacity = 1// 设置新的透明度this.setState({opacity})}, 200);}// 组件将要卸载componentWillUnmount(){// 清除定时器clearInterval(this.timer)}// 初始化渲染,和状态更新的时候会被调用render(){return (<div><h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2><button onClick={this.death}>不活了</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Life/>, document.getElementById('test'))</script>
</body>
</html>
5.2 生命周期(旧)
5.2.1 流程图
- 新版本:提出了两个新的钩子,废弃了两个钩子
- 总共有四种方式会触发钩子:
- 组件挂载时
- 父组件render
- setState
- forceUpdate:不想让状态更新,但是想让页面刷新
componentWillReceiveProps:组件第一次render的时候不会被调用,而在第二次开始才会被调用
5.2.2 示例代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>组件生命周期</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --><script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Count extends React.Component {constructor(props){console.log("render - constructor")super(props)// 初始化状态this.state = {count: 0, content: "content"}}add = () => {this.setState({count: this.state.count + 1})}// 卸载组件按钮回调death = () => {ReactDOM.unmountComponentAtNode(document.getElementById('test'))}// 强制更新的按钮回调force = () => {this.forceUpdate()}// 修改子组件的内容changeContent = () => {this.setState({"content": "content1"})}// 组件将要挂载的钩子componentWillMount(){console.log("render - componentWillMount")}// 组件挂载完毕的钩子componentDidMount(){console.log("render - componentDidMount")}// 组件将要卸载的钩子componentWillUnmount(){console.log("render - componentWillUnmount")}// 控制组件是否更新的钩子(返回的布尔类型,如果是false,那么不会继续往下走)shouldComponentUpdate(){console.log("render - shouldComponentUpdate")return true}// 组件将要更新的钩子componentWillUpdate(){console.log("render - ComponentWillUpdate")}// 组件完成更新的钩子componentDidUpdate(){console.log("render - componentDidUpdate")}// 初始化渲染,和状态更新的时候会被调用render(){console.log("render")return (<div><h2>当前求和为:{this.state.count}</h2><button onClick={this.add}>点我+1</button><button onClick={this.death}>卸载组件</button><button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button><button onClick={this.changeContent}>修改传入子组件的参数内容</button><Child content={this.state.content} /></div>)}}class Child extends React.Component{// 第二次开始接收父组件参数之前的钩子componentWillReceiveProps(props){console.log("render - componentWillReceiveProps", {props})}render(){return <h1>子组件的内容:{this.props.content}</h1>}}// 2.渲染组件到页面ReactDOM.render(<Count/>, document.getElementById('test'))</script>
</body>
</html>
5.3 生命周期(新)
- 废弃三种钩子:
- componentWillMount
- componentWillUpdate
- componentWillReceiveProps
- 新增两种钩子,但是不常用:
- getDerivedStateFromProps:如果state的值在任何时候都取决于props,那么可以用该钩子。如果使用了该组件,会导致代码难维护。
- getSnapshotBeforeUpdate:在组件渲染准备渲染前,可以拿到渲染前的组件的props和state,用于和渲染后的props和state做一些组件高度等对比,实现某些场景功能。
5.3.1 流程图
从React18开始,有三个钩子的函数名称需要加UNSAFE_前缀(除了最后的销毁的钩子,其他名称有WILL的名称的钩子,那么都需要加上UNSAFE_)
1.UNSAFE_componentWillMount
2.UNSAFE_componentWillUpdate
3.UNSAFE_componentWillReceiveProps
加上UNSAFE_的原因:由于后续需要推出异步渲染,如果使用上述三种,很有可能会出现其他的问题
5.3.2 代码案例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>组件生命周期</title>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --><script type="text/javascript" src="../js/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class Count extends React.Component {constructor(props){console.log("render - constructor")super(props)// 初始化状态this.state = {count: 0}}add = () => {this.setState({count: this.state.count + 1})}// 卸载组件按钮回调death = () => {ReactDOM.unmountComponentAtNode(document.getElementById('test'))}// 强制更新的按钮回调force = () => {this.forceUpdate()}// 修改子组件的内容changeContent = () => {this.setState({"content": "content1"})}// 组件挂载完毕的钩子componentDidMount(){console.log("render - componentDidMount")}// 组件将要卸载的钩子componentWillUnmount(){console.log("render - componentWillUnmount")}// 控制组件是否更新的钩子(返回的布尔类型,如果是false,那么不会继续往下走)shouldComponentUpdate(){console.log("render - shouldComponentUpdate")return true}// 组件完成更新的钩子componentDidUpdate(preProps, preState, snapshotValue){console.log("render - componentDidUpdate")}static getDerivedStateFromProps(props, state){console.log("render - getDerivedStateFromProps")return props}getSnapshotBeforeUpdate(){console.log("render - getSnapshotBeforeUpdate")return "该值会传入给 componentDidUpdate钩子"}// 初始化渲染,和状态更新的时候会被调用render(){console.log("render")return (<div><h2>当前求和为:{this.state.count}</h2><button onClick={this.add}>点我+1</button><button onClick={this.death}>卸载组件</button></div>)}}// 2.渲染组件到页面ReactDOM.render(<Count/>, document.getElementById('test'))</script>
</body>
</html>
5.3.2 getSnapshotBeforeUpdate-案例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>组件生命周期</title><style>.list{width: 200px;height: 150px;background-color: skyblue;overflow: auto;}.news{height: 30px;}</style>
</head>
<body><!-- 创建一个容器 --><div id="test"></div><!-- 引入react核心库,全局会多了一个对象React --><script type="text/javascript" src="../js/react17/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM --><script type="text/javascript" src="../js/react17/react-dom.development.js"></script><!-- 引入babel,用于将jsx转化为js --><script type="text/javascript" src="../js/react17/babel.min.js"></script><!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes --><script type="text/javascript" src="../js/react17/prop-types.js"></script><!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 --><script type="text/babel">// 1.创建类式组件class NewsList extends React.Component {state = {newsArr:[]}componentDidMount(){setInterval(() => {const newsArr = this.state.newsArr// 模拟一条新闻const news = '新闻' + (newsArr.length + 1)// 更新状态this.setState({newsArr:[news, ...newsArr]})}, 1000)}getSnapshotBeforeUpdate(){return this.refs.list.scrollHeight}componentDidUpdate(preProp, preState, height){this.refs.list.scrollTop += this.refs.list.scrollHeight - height}// 初始化渲染,和状态更新的时候会被调用render(){return (<div className="list" ref="list">{this.state.newsArr.map((n, index) => {return <div className="news" key={index}>{n}</div>})}</div>)}}// 2.渲染组件到页面ReactDOM.render(<NewsList/>, document.getElementById('test'))</script>
</body>
</html>
5.4 DOM的diffing算法
- 虚拟DOM的key的作用:
- 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
- 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
- (1)若虚拟DOM中内容没变,直接使用之前的真实DOM
- (2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- b.旧虚拟DOM中未找到与新虚拟DOM相同的key。根据数据创建新的真实DOM,随后渲染到到页面。
- a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 用index作为key可能会引发的问题:
- 1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新,界面效果没问题,但效率低。
- 2.如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
- 3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
使用index索引值做为key导致的问题:导致key会频繁的变化,导致页面不断地重新渲染初始数据:{id:1,name:'小1',age:18},{id:2,name:'小2',age:19},
初始的虚拟DOM:<li key=0>小1 --- 18</li><li key=1>小2 --- 19</li>更新后的数据:{id:3,name:'小3',age:20},{id:1,name:'小1',age:18},{id:2,name:'小2',age:19},
更新后的的虚拟DOM:<li key=0>小3 --- 20</li><li key=1>小1 --- 18</li><li key=2>小2 --- 19</li>
- 开发中如何选择key?:
- 1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
- 2.如果确定只是简单的展示数据,用index也是可以的。
四、react应用(基于react脚手架)
包安装
# 安装具体大版本react:npm add react@16
# 安装最新版本:npm add react
1.创建react应用
- 使用create-react-app模块创建应用
1.1 react脚手架
- xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目
- a.包含了所有需要的配置(语法检查、jsx编译、devServer…)
- b.下载好了所有相关的依赖
- c.可以直接运行一个简单效果
- react 提供了一个用于创建react 项目的脚手架库:create-react-app
- 项目的整体技术架构为:react +webpack+ es6+ eslint
- 使用脚手架开发的项目的特点:模块化,组件化,工程化
1.2 创建项目并启动
注意:使用npm需要安装node,我安装的版本是v20.18.0
- 第一步:全局安装:npm i -g create-react-app
- 第二步:切换到想创建项目的目录,使用命令:create-react-app hello_react
- 第三步:进入项目文件夹:cd hello_react
- 第四步:启动项目:npm start
如果需要降级react版本:
npm i react-dom@16.x --legacy-peer-deps
npm i react@16.x --legacy-peer-deps
1.3 项目结构
- public:静态资源文件夹
- favicon.icon:网站页签标题
index.html
:主页面(核心文件,有个id=root的div标签)- logo192.png:192x192的logo图
- logo512.png:512x512的logo图
- mainfest.json:应用加壳的配置文件(不使用加壳,可以删除)
- robots.txt:爬虫协议文件
- src:源码文件夹
- App.css:App组件的样式
Apps.js
:App组件:是一个主组件,会被index.js挂载到index.html的id=root的div节点里面- App.test.js:用于给App做测试(几乎不用,可以删除)
- index.css:样式
index.js
:入口文件(引入了React相关库,会把App.js的主组件放到index.html的id=root的div节点里面)- logo.svg:logo图
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" /><!-- 描述信息,搜索引擎收录我们网页信息的时候有用 --><metaname="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>
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';const root = ReactDOM.createRoot(document.getElementById('root'));root.render(// 检查<App/>的所有子组件里面是否写的合理(例如某些语法是过期了)<React.StrictMode><App /></React.StrictMode>
);reportWebVitals();
2.组件目录定义和导入
3.样式模块化
- 为什么需要模块化?防止不同组件间,定义了相同的标签的样式,会互相影响。
错误案例
.title{background-color: orange;
}
第一种方法,使用组件名称(常用)
.component_name{.title{background-color: orange;}
}
第二种方法,导包传入(不常用)
- 步骤:
- index和css中间加module命名
- 导入css样式,需要拿变量接收,接完了会变成一个对象
- 将对象写入到对应的标签
4.vscode安装react插件
快捷键:rcc:类组件模板rfc:函数式组件模板
5.组件化编码流程
- 编码流程:
- 拆分组件:拆分界面,抽取组件
- 实现静态组件:使用组件实现静态页面效果
- 实现动态组件:
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件?
- 交互(从绑定时间监听开始)
- 动态显示初始化数据
6.组件组合使用-TodoList案例
- 需求:
- 显示所有todo列表
- 输入文本,点击按钮显示到列表的首位,并清除输入的文本
6.1 拆组件
6.2 依赖安装
# 安装uuid(比uuid库更小)
npm add nanoid
# 安装类型验证库
npm add prop-types
6.3 总结
- 代码包在资源里面:1.TODO-LIST案例.zip
- 相关知识点:
- 1.拆分组件、实现静态组件,注意:className、style的写法
- 2.动态初始化列表,如何确定将数据放在哪个组件的state中?
- 某个组件使用:放在其自身的state中
- 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 3.关于父子之间通信
- 1.【父组件】给【子组件】传递数据:通过props传递
- 2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue和 value
- 5.状态在哪里,操作状态的方法就在哪里
五、react-axios
- 说明:
- React 本身只关注于界面,并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react 应用中需要集成第三方 ajax 库(或自己封装)
- 常用的ajax请求库
- jQuery:比较重,如果需要另外引入不建议使用
- axios:轻量级,建议使用
- 封装XmlHttpRequest对象的ajax
- promise风格
- 可以用在浏览器端和node服务器端
1.安装axios
npm add axios
2.代理配置
- 解决开发的时候,跨域问题
2.1 方法一(不使用)
- 只有react服务器没有的资源,才会通过代理转发。会造成问题:
- 如果访问:/index.html 会访问到react的public的index.html文件,而不是服务端的资源
- 如果访问:/index.html 会访问到react的public的index.html文件,而不是服务端的资源
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000(优先匹配前端资源)
2.2 方法二(常用)
- src/setupProxy.js:是一个React脚手架默认加载的文件,不能使用ES6语法去写,需要用CJS语法。React会将这个配置文件加载webpack里面。
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
第二步:编写setupProxy.js配置具体代理规则
const proxy = require('http-proxy-middleware')module.exports = function(app){app.use(// 要把哪些前缀的api代理转发到目标服务器// http-proxy-middleware 2以下版本写法:proxy.createProxyMiddleware('/api1',{})// 下面是http-proxy-middleware 2以上版本写法proxy.createProxyMiddleware('/api1',{target:'http://localhost:5000', // 请求转发给谁changeOrigin:true, // 控制服务器收到的请求头中Host的值(建议必须)/*changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000changeOrigin默认值为false,但我们一般将changeOrigin值设为true*/pathRewrite:{'^/api1': ''} // 重写请求路径(必须)}),proxy.createProxyMiddleware('/api2',{target:'http://localhost:5001', changeOrigin:true, pathRewrite:{'^/api2': ''}}))
}
3.github搜索案例
- 代码包在资源里面:2.github搜索案例.zip
axios使用案例
import React, { Component } from 'react'
import axios from 'axios'export default class Search extends Component {search = ()=>{const {keyWordNode:{value:keyWord}} = thisconsole.log(keyWord)// 发送网络请求axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(response => {console.log('成功了', this.props.loadUserList(response.data.items))},error => {console.log('失败了', error)})}render() {return (<section className='jumbotron'><h3 className='jumbotron-heading'>搜索Github用户</h3><div><input ref={c => this.keyWordNode = c} type='text' placeholder='请输入关键词点击搜索' /> <button onClick={this.search}>搜索</button></div></section>)}
}
六、react-router
1.相关理解
1.1 SPA理解
- 单页Web应用(single page web application, SPA)
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取,并在前端异步展现。
1.2 路由的理解
- 什么是路由?
- 一个路由就是一个映射关系(key: value)
- key为路径,value可能是function或component
- 路由分类
- 后端路由:
- value是function,用来处理客户端提交的请求。
- 注册路由:router.get(path,function(req,res))
- 工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由
- 浏览器端路由,value 是component,用于展示页面内容。↵
- 注册路由: <Route path="/test"component={Test}>↵
- 工作过程:当浏览器的 path 变为/test 时,当前路由组件就会变为Test 组件
- 后端路由:
1.3 react-router-dom的理解
- react的一个插件库。
- 专门用来实现一个SPA 应用。
- 基于react的项目基本都会用到此库。
2.react-router-dom
- 官网:http://react-router.docschina.org/
- 总共有三个类别:
- WEB:web开发使用
- NATIVE:移动端开发使用
- ANYWHERE:web开发和移动端都可以使用,但是最好不使用这个,API会比较复杂。
2.1 安装
# 由于react-router-dom在2021年11月份升级到了6版本,如果安装5版本需要使用如下命令
npm add react-router-dom@5
2.2 路由组件使用
- 路由器两种:
- BrowserRouter:使用的是H5的history API,不兼容IE9及以下版本。路径中没有#,例如:localhost:3000/demo/test
- HashRouter:路径包含#,例如:localhost:3000/#/demo/test
- 注意:路由的切换和注册,需要在同一个路由器的标签内。
- 一般是在<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
- 路由跳转:<Link to “/xxxx” component={Demo}>
- 路由注册(页面展示的地方):<Route path=‘/xxxx’ component={Demo} >
import React, { Component } from 'react'
import {Link,BrowserRouter,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'export default class App extends Component {loadUserList = (userList)=>{this.setState({userList: userList})}render() {return (<div><div className='row'><div className='col-xs-offset-2 col-xs-8'><div className='page-header'><h2>React Router Demo</h2></div></div></div><BrowserRouter><div className='row'><div className='col-xs-2 col-xs-offset-2'><div className='list-group'>{/* 在原生html中,靠<a>标签跳转不同的页面 */}{/* <a className='list-group-item' href='./about.html'>About</a><a className='list-group-item active' href='./home.html'>Home</a> */}{/* 在React中靠路由连接实现切换组件 */}<Link className='list-group-item' to='/about'>About</Link><Link className='list-group-item' to='/home'>Home</Link></div></div><div className='col-xs-6'><div className='panel'><div className='panel-body'>{/* 注册路由 */}<Route path="/about" component={About}></Route><Route path="/home" component={Home}></Route></div></div></div></div></BrowserRouter></div>)}
}
2.3 路由组件和一般组件的区别
- 写法不同:
- 一般组件:<Demo/>
- 路由组件:<Route path=‘/demo’ component={Demo}/ >
- 存放位置不同:
- 一般组件:components
- 路由组件:pages
- 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:会收到三个参数:history、location、match
- history:
- go(n):回退n步:
- n > 0:前进n步
- n < 0:回退n步
- goBack():回退1步
- goForward():前进1步
- push(path,state):跳转(留痕)
- replace(path,state):跳转(不留痕)
- go(n):回退n步:
- location:
- pathname: 路由地址
- search: 路由组件传入的search参数
- state:路由组件传入的state参数
- math:
- params:params参数
- path:路由地址
- url:路由地址
- history:
- 借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()-
- his.prosp.history.replace()
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go()
2.4 NavLink
- 解决点击了路由,对应页面的按钮需要高亮
- 原理是给当前的按钮的class加了一个active类名
- 如果不是active类名,而是其他的,可以通过参数activeClassName控制
<NavLink activeClassName='active' className='list-group-item' to='/about'>About</NavLink>
2.5 封装NavLink组件
- 需求:解决一些NavLink的属性重复编写的问题
- 总结:
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
- 标签体内容是一个特殊的标签属性,通过this.props.children可以获取标签体内容
封装
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'export default class MyNavLink extends Component {render() {return (<NavLink activeClassName='active' className='list-group-item' {...this.props}/>)}
}
使用
/*下面两个效果一样*/
<MyNavLink to='/home'>Home</MyNavLink>
<MyNavLink to='/home' chilren='Home'/>
细节:MyNavLink标签中间的Home标题,他会作为一个组件的children参数传入到this.props参数里面
2.6 Switch使用
- 通常情况下,path和component是一一对应的关系。
- 如果出现了两个路由的path是相同的,但是component是不同的,那么默认会都展示出来两个组件
<Route path="/about" component={About}></Route>
<Route path="/home" component={Test}></Route>
<Route path="/home" component={Home}></Route>
- Switch组件:如果从上到下,匹配到了一个path,那么就不会继续往下进行匹配
- 使用场景:一个路由组件以上使用
import {Switch,BrowserRouter,Route} from 'react-router-dom'
<Switch><Route path="/about" component={About}></Route><Route path="/home" component={Test}></Route><Route path="/home" component={Home}></Route>
</Switch>
2.7 样式丢失
- 问题:如果path加了前缀的时候,刷新,请求资源的时候,也会加上前缀
- 原因:引入css的时候,不能使用:./css/xxxx.css
- 解决办法:
- 1.去掉点:/css/bootstrap.css
- 2.换成PUBLIC_URL:%PUBLIC_URL%/css/bootstrap.css
2.8 路由模糊匹配
- 默认是开启前缀模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:<Route exact={true} path=“/about” component={About}/ >
严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
<MyNavLink to='/home/a/b/c'>Home</MyNavLink>
// 上面可以匹配到下面的路由
<Route path="/home" component={Home}></Route>
- 开启精准匹配
<MyNavLink to='/home'>Home</MyNavLink>
<Route exact={true} path="/home" component={Home}></Route>
2.9 Redirect重定向
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch><Route path="/about" component={About}></Route><Route exact={true} path="/home" component={Home}></Route><Redirect to="/about"/>
</Switch>
2.10 嵌套(多级)路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
一级路由
<MyNavLink to='/home'>Home</MyNavLink>
<Switch><Route path="/home" component={Home}></Route><Redirect to="/about"/>
</Switch>
二级路由
<MyNavLink to="/home/news">News</MyNavLink>
<Switch><Route path="/home/news" component={News}></Route>
</Switch>
2.11 向路由组件传递参数数据
- 使用频率(高->低):params参数、search参数(需要解析参数)、state参数(敏感信息传递可以用这种)
一级路由
<MyNavLink to='/home'>Home</MyNavLink>
<Switch><Route path="/home" component={Home}></Route><Redirect to="/about"/>
</Switch>
二级路由
<MyNavLink to="/home/news">News</MyNavLink>
<Switch><Route path="/home/news" component={News}></Route>
</Switch>
2.11.1 传递params参数
- 路由链接(携带参数):<Link to=‘/demo/test/tom/18’}>详情</Link>
- 注册路由(声明接收):<Route path="/demo/test/:name/:age"component={Test}/>
- 接收参数:const{id,title}=this.props.match.params
Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'export default class Message extends Component {state = {messageArr:[{id:'01',title:'消息1'},{id:'02',title:'消息2'},{id:'03',title:'消息3'},]}render() {const {messageArr} = this.statereturn (<div><ul>{messageArr.map((msgObj) => {return (<li key={msgObj.id}>{/* 向路由组件传递params参数 */}<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link></li>)})}</ul><hr/>{/* 声明接收params参数 */}<Route path="/home/message/detail/:id/:title" component={Detail}/></div>)}
}
Message详情
import React, { Component } from 'react'export default class Detail extends Component {render() {const {id,title} = this.props.match.paramsreturn (<ul><li>ID:{id}</li><li>TITLE:{title}</li></ul>)}
}
2.11.2 传递search参数
- 路由链接(携带参数):<Link to=‘/demo/test?name=tom&age=18’}>详情</Link>
- 注册路由(无需声明,正常注册即可):<Route path="/demo/test”component={Test}/>
- 接收参数:const{search}=this.props.location
- 备注:获取到的search是一个参数字符串,需要自己另外处理转化为对象
Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'export default class Message extends Component {state = {messageArr:[{id:'01',title:'消息1'},{id:'02',title:'消息2'},{id:'03',title:'消息3'},]}render() {const {messageArr} = this.statereturn (<div><ul>{messageArr.map((msgObj) => {return (<li key={msgObj.id}>{/* 向路由组件传递search参数 */}<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link></li>)})}</ul><hr/>{/* 无需声明接收search参数 */}<Route path="/home/message/detail" component={Detail}/></div>)}
}
Message详情
import React, { Component } from 'react'function queryUrl (search) {let obj = {}let url = search.slice(1)let arr = url.split('&') //[name=jack,age=18]arr.forEach(item => {let newArr = item.split('=') //[name,jack] [age,18]obj[newArr[0]] = newArr[1]})return obj}export default class Detail extends Component {render() {const {search} = this.props.locationconst {id,title} = queryUrl(search)return (<ul><li>ID:{id}</li><li>TITLE:{title}</li></ul>)}
}
2.11.3 传递state参数
- 路由链接(携带参数):<Link to={{path:‘/demo/test’,state:{name:‘tom’,age:18}}}>详情</Link>
- 注册路由(无需声明,正常注册即可):<Route path="/demo/test"component={Test}/>
- 接收参数:this.props.location.state
- 备注:刷新也可以保留住参数
Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'export default class Message extends Component {state = {messageArr:[{id:'01',title:'消息1'},{id:'02',title:'消息2'},{id:'03',title:'消息3'},]}render() {const {messageArr} = this.statereturn (<div><ul>{messageArr.map((msgObj) => {return (<li key={msgObj.id}>{/* 向路由组件传递state参数 */}<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link></li>)})}</ul><hr/>{/* 无需声明接收state参数 */}<Route path="/home/message/detail" component={Detail}/></div>)}
}
Message详情
import React, { Component } from 'react'export default class Detail extends Component {render() {// 接收state参数const {id,title} = this.props.location.statereturn (<ul><li>ID:{id}</li><li>TITLE:{title}</li></ul>)}
}
2.12 路由跳转的两个模式
- 两个模式的区别:
- push模式是默认的模式,路由跳转会留下痕迹
- replace模式跳转不会留下痕迹
2.12.1 push模式
- 默认的路由组件,就是push模式
2.12.2 replace模式
<Link replace to="/home/message/detail">{msgObj.title}</Link>
2.13 编程式路由导航
- 需求:
- A组件展示后等三秒钟,跳转到B组件跳转
- 跳转可以点击按钮是push模式 还是replace模式
- 实现:回退、前进
- 路由组件:
- history:
- go(n):回退n步:
- n > 0:前进n步
- n < 0:回退n步
- goBack():回退1步
- goForward():前进1步
- push(path,state):跳转(留痕)
- replace(path,state):跳转(不留痕)
- go(n):回退n步:
- history:
- 借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()-
- his.prosp.history.replace()
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go()
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'export default class Message extends Component {state = {messageArr:[{id:'01',title:'消息1'},{id:'02',title:'消息2'},{id:'03',title:'消息3'},]}replaceShow = (id,title)=>{// replace跳转+params参数this.props.history.replace(`/home/message/detail/${id}/${title}`)// replace跳转+携带search参数// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)// replace跳转+携带state参数// this.props.history.replace(`/home/message/detail`, {id,title})}pushShow = (id,title)=>{// push跳转+params参数this.props.history.push(`/home/message/detail/${id}/${title}`)// push跳转+携带search参数// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)// push跳转+携带state参数// this.props.history.replace(`/home/message/detail`, {id,title})}back = () => {this.props.history.goBack()}forward = () => {this.props.history.goForward()}render() {const {messageArr} = this.statereturn (<div><ul>{messageArr.map((msgObj) => {return (<li key={msgObj.id}>{/* 向路由组件传递参数 */}<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> <button onClick={()=>{this.pushShow(msgObj.id,msgObj.title)}}>push查看</button> <button onClick={()=>{this.replaceShow(msgObj.id,msgObj.title)}}>replace查看</button></li>)})}</ul><hr/>{/* 需声明接收params参数 */}<Route path="/home/message/detail/:id/:title" component={Detail}/>{/* search参数无需声明接收,正常注册路由即可*/}{/* <Route path="/home/message/detail" component={Detail}/> */}{/* state参数无需声明接收,正常注册路由即可*/}{/* <Route path="/home/message/detail" component={Detail}/> */}<button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button></div>)}
}
2.14 让一般组件也有路由组件的参数-withRouter
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'class Header extends Component {back = () => {this.props.history.goBack()}render() {return (<div><div><h2>React Router Demo</h2></div></div>)}
}// 让一般组件,也有路由组件的三个props属性
export default withRouter(Header)
2.15 BrowserRouter与HashRouter的区别
- 1.底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
- HashRouter使用的是URL的哈希值。
- 2.ur1表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 3.刷新后对路由state参数的影响
- (1).BrowserRouter没有任何影响,因为state保存在history对象中。
- (2).HashRouter刷新后会导致路由state参数的丢失。
- 4.备注:HashRouter可以用于解决一些路径错误相关的问题。
- 5.BrowerRouter用得比较多
七、组件库-Ant Design
- web开发推荐Ant Design:
- 官网地址:https://ant-design.antgroup.com/index-cn
- 移动端开发推荐有赞团队的Vant:
- 官网地址:https://vant-ui.github.io/vant/#/zh-CN
1.安装
# 如果要安装4版本的主要执行:npm install antd@4
npm install antd
2.入门体验
- 注意:如果是4版本的,需要引入css样式,如果是5版本的antd,只需要导入组件,就有对应的样式。下面是用antd4版本案例
import React, { Component } from 'react'
import { Button} from 'antd'
import {WechatOutlined
} from '@ant-design/icons';
import 'antd/dist/antd.css'export default class App extends Component {render() {return (<div><Button type="primary" icon={<WechatOutlined />}>点我</Button></div>)}
}
3.按需引入样式
- import ‘antd/dist/antd.css’ 代表将所有antd的css样式都引入来了。
- 3.x版本的教程,会更加详细,4.x省略了一些
- 4.x按需引入样式教程:https://3x.ant.design/docs/react/use-with-create-react-app-cn
- 在5.x版本,不需要再手工引入样式,框架自己处理了。
4.自定义主题
- antd默认主题色是支付宝的蓝色,下面是修改默认主题色
- 注意:
- 4.x版本使用了less和css变量,需要安装包
- 5.x版本使用了CSS-in-JS,不需要另外安装包
下面是4.x版本的教程
4.1 安装包
- npm add react-app-rewired customize-cra
- npm add craco-less
- npm add babel-plugin-import
- 教程来源于3.x
4.2 修改package.json
- 需改里面scripts
4.3 创建配置文件
- 然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。
const { override, fixBabelImports, addLessLoader } = require('customize-cra');module.exports = override(fixBabelImports('import', {libraryName: 'antd',libraryDirectory: 'es',style: true,}),addLessLoader({lessOptions:{javascriptEnabled: true,modifyVars: { '@primary-color': 'orange' },}}),
);
八、redux
- 文档:
- 英文:https://redux.js.org/
- 中文文档:https://redux.org.cn
1.理解
1.1 redux是什么
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react,angular,vue等项目中,但基本与react配合使用。
作用:集中式管理 react 应用中多个组件共享的状态。
1.2 使用场景
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
总体原则:能不用就不用,如果不用比较吃力才考虑使用。
2.redux的核心API
- 注意:
在reducer中,不要使用在某种情况下,不会更改state的逻辑代码。例如,某个数为偶数的时候,才加1。
- 逻辑代码需要在业务层控制好,reducer只负责加工state数据。
2.1 安装redux
下面案例都是使用redux4进行使用
# 如果需要安装redux4版本:npm add redux@4
npm add redux
2.2 redux工作流程
- 流程:
- React Components需要修改state,通过Action对象告诉Store,Action对象里面有type和data。
- React Components需要修改state,通过Action对象告诉Store,Action对象里面有type和data。
- action:一个动作一个
- 动作的对象
- 它可以是两种类型:
- Object:同步action
- function:异步action
- 包含两个属性:
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值类型任意,可选属性
- 例子:{type:‘ADD_STUDENT’,data:{name:‘tom’,age:18}}
- reducer:每个组件都有一个
- 用于初始化状态,加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
- store:一个项目就一个
- 存放公共state的地方
2.3 使用案例
2.3.1 求和案例-纯react版本
import React, { Component } from 'react'export default class Count extends Component {state = {count:0}// 加法increment = ()=>{const {value} = this.selectNumberconst {count} = this.statethis.setState({"count":count+value*1})}// 减法decrement = ()=>{const {value} = this.selectNumberconst {count} = this.statethis.setState({"count":count-value*1})}// 奇数再加incrementIfOdd = ()=>{const {value} = this.selectNumberconst {count} = this.stateif(count%2 !== 0){this.setState({"count":count+value*1})}}// 异步加incrementAsync = ()=>{const {value} = this.selectNumberconst {count} = this.statesetTimeout(()=>{this.setState({"count":count+value*1})},500)}render() {return (<div><h1>当前求和为:{this.state.count}</h1><select ref={c => this.selectNumber = c}><option value="1">1</option><option value="2">2</option><option value="3">3</option></select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button></div>)}
}
2.3.2 求和案例-redux精简版
- 将组件组件注册到store,数据更改了会被通知(主要是重新调用组件render)。两种方式
- 1.在每个组件的componentDidMount函数中注册
- 2.在index.js中注册
- 步骤:
- 1.编写reducer
- 2.编写store,将reducer注册
- 3.在代码中使用store.getState获取状态
- 4.在代码中使用store.dispatch({type:‘increment’,data:value*1}) action让store告诉reducer加工数据。
- 5.在组件订阅store。(如果在index.js订阅了,就需要这一步骤)
count_reducer.js
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){//从action对象中获取:type、dataconst {type,data} = action//根据type决定如何加工数据switch (type){case 'increment':// 加法return preState + datacase 'decrement':// 减法return preState - datadefault:return preState}
}
store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
index.js
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';ReactDOM.render(<App/>,document.getElementById('root'))store.subscribe(()=>{ReactDOM.render(<App/>,document.getElementById('root'))
})
coponents/Count/index.jsx
import React, { Component } from 'react'
//引入store,用户获取redux获取的状态
import store from '../../redux/store'
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'export default class Count extends Component {// componentDidMount(){// // 检测redux中状态的变化,只要变化,就调用render// store.subscribe(()=>{// // 只要调用了setState就调用render// this.setState({})// })// }// 加法increment = ()=>{const {value} = this.selectNumberstore.dispatch(createIncrementAction(value*1))}// 减法decrement = ()=>{const {value} = this.selectNumberstore.dispatch(createDecrementAction(value*1))}// 奇数再加incrementIfOdd = ()=>{const {value} = this.selectNumberconst count = store.getState()if(count%2 !== 0){store.dispatch(createIncrementAction(value*1))}}// 异步加incrementAsync = ()=>{const {value} = this.selectNumberconst count = store.getState()setTimeout(()=>{store.dispatch(createDecrementAction(value*1))},500)}render() {return (<div><h1>当前求和为:{store.getState()}</h1><select ref={c => this.selectNumber = c}><option value="1">1</option><option value="2">2</option><option value="3">3</option></select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button></div>)}
}
2.3.3 求和案例-redux完整版
- 比精简版多了以下内容:
- 新增文件:
- 1.count_action.js:专门用于创建action对象
- 2.constant.js:放置容易写错的type值
constant.js
- 新增文件:
/*该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词*/
export const INCREAMENT = 'increment'
export const DECREAMENT = 'increment'
count_action.js
/* 该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})
count_reducer.js
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "./constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){//从action对象中获取:type、dataconst {type,data} = action//根据type决定如何加工数据switch (type){case INCREAMENT:// 加法return preState + datacase DECREAMENT:// 减法return preState - datadefault:return preState}
}
store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
index.js
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';ReactDOM.render(<App/>,document.getElementById('root'))store.subscribe(()=>{ReactDOM.render(<App/>,document.getElementById('root'))
})
coponents/Count/index.jsx
- 2.3.2一样
2.4 异步action
异步action不是一定要用的东西,在组件里面,通过定时器实现,也是可以
- action返回的是Object,那么就是同步action。返回的是一个函数,那么就是异步action。
- 因为函数里面可以开启异步任务
2.4.1 安装
# 如果是rect16,安装命令:npm add redux-thunk@2.3.0
# 如果是react最新版本,安装命令:npm add redux-thunk
2.4.2 注册中间件
store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"//暴露store,注册异步action的异步中间件
export default createStore(countReducer,applyMiddleware(thunk))
2.4.3 异步aciton定义
/* 该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"
import store from "./store"//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})//异步action,就是指action的值为函数;异步action中一般都会调用同步action
export const createIncrementAsyncAction = (data,time) => {return (dispatch)=>{setTimeout(()=>{// 通知redux加上datadispatch(createIncrementAction(data))},time)}
}
九、react-redux
- 目的:不让开发者随意的在UI组件里面随意和redux进行交互,而是通过UI组件外面那一层容器进行交互。
1.模型图
- 1.所有的UI组件都应该包裹一个容器组件,他们是父子关系。
- 2.容器组件是真正和redux打交道的,里面可以随意的使用redux的api。
- 3.Ul组件中不能使用任何redux的api。
- 4.容器组件会传给UI组件:(1).redux中所保存的状态。(2).用于操作状态的方法
- 5.备注:容器给Ul传递:状态、操作状态的方法,均通过props传递。
2.安装
# react16安装:npm add react-redux@7.2.2
# 下面是react最新版安装命令
npm add react-redux
3.相关概念
- (1)明确两个概念:
- 1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
- 2)容器组件:负责和redux通信,将结果交给UI组件。
- (2)如何创建一个容器组件-—靠react-redux 的connect函数
- connect(mapStateToProps,mapDispatchToProps)(UI组件)
- mapStateToProps:映射状态,返回值是一个对象
- mapDispatchToProps:映射操作状态的方法,返回值是一个对象
- connect(mapStateToProps,mapDispatchToProps)(UI组件)
- (3)备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- (4)备注2:mapDispatchToProps,也可以是一个对象
4.基础操作(单组件)
4.1 index.js
- 监控redux状态变化
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';ReactDOM.render(<App/>,document.getElementById('root'))// 监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(()=>{ReactDOM.render(<App/>,document.getElementById('root'))
})
4.2 redux相关
store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"//暴露store,注册异步action的异步中间件
export default createStore(countReducer,applyMiddleware(thunk))
constant.js
/*该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词*/
export const INCREAMENT = 'increment'
export const DECREAMENT = 'decrement'
count_actino.js
/* 该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})//异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data,time) => {return (dispatch)=>{setTimeout(()=>{// 通知redux加上datadispatch(createIncrementAction(data))},time)}
}
count_reducer.js
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "./constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){//从action对象中获取:type、dataconst {type,data} = action//根据type决定如何加工数据switch (type){case INCREAMENT:// 加法return preState + datacase DECREAMENT:// 减法return preState - datadefault:return preState}
}
4.3 容器
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'/* 映射状态1.mapStateToProps函数返回是一个对象2.返回得对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){// 等于:<CountUI n={900}/>return {count:state}
}/* 映射操作状态得方法1.mapDispatchToProps函数返回是一个对象2.返回得对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value3.mapDispatchToProps用于传递状态
*/
function mapDispatchToProps(dispatch){return {jia:(number)=>{//通知redux执行加法dispatch(createIncrementAction(number))},jian:(number)=>{//通知redux执行减法dispatch(createDecrementAction(number))},jiaAsync:(number,time)=>{//通知redux执行异步加dispatch(createIncrementAsyncAction(number,time))},}
}// 创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
4.4 UI组件
import React, { Component } from 'react'export default class Count extends Component {// 加法increment = ()=>{const {value} = this.selectNumberthis.props.jia(value*1)}// 减法decrement = ()=>{const {value} = this.selectNumberthis.props.jian(value*1)}// 奇数再加incrementIfOdd = ()=>{const {value} = this.selectNumberif(this.props.count % 2 !== 0){this.props.jia(value*1)}}// 异步加incrementAsync = ()=>{const {value} = this.selectNumberthis.props.jiaAsync(value*1,500)}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> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button></div>)}
}
5.基础操作(代码优化)
- 优化内容:
- 容器组件和UI组件整合成一个文件
- 无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
- 使用了react-redux后也不用自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
- mapDispatchToProps也可以简单的写成一个对象
- 一个组件要和redux“打交道”要经过那几步?
- 1.定义好UI组件(不暴露)
- 2.引入connect生成一个容器组件,并暴露。
- 3.在UI组件中通过this.props.xxx读取和操作状态
5.1 容器优化
5.2 redux状态变化机制
- 由于使用了react-redux,当状态变化了,会自动重新渲染组件
- 原理:由于我们使用connect,react-redux在里面做了状态监听渲染
- 原理:由于我们使用connect,react-redux在里面做了状态监听渲染
5.3 容器传入store优化-Provider
- Provider:会分析所有的容器组件,将store传入所有的store组件
5.4 整合UI组件和容器组件
- 将UI组件和容器组件整合到一个文件里面,整合的内容还是叫容器
import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'// 定义UI组件
class Count extends Component {// 加法increment = ()=>{const {value} = this.selectNumberthis.props.jia(value*1)}// 减法decrement = ()=>{const {value} = this.selectNumberthis.props.jian(value*1)}// 奇数再加incrementIfOdd = ()=>{const {value} = this.selectNumberif(this.props.count % 2 !== 0){this.props.jia(value*1)}}// 异步加incrementAsync = ()=>{const {value} = this.selectNumberthis.props.jiaAsync(value*1,500)}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> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button></div>)}
}// 创建并暴露一个Count的容器组件
export default connect(state => ({count:state}),{jia:createIncrementAction,jian:createDecrementAction,jiaAsync:createIncrementAsyncAction,}
)(Count)
6.基础操作(多组件)数据共享
6.1 实现效果
6.2 redux目录结构
6.3 相关代码
- 总结:
- 定义一个Pserson组件,和Count组件通过redux共享数据。
- 为Person组件编写: reducer、action,配置constant常量。
- 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
- 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。
App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'export default class App extends Component {render() {return (<div><Count/><hr/><Person/></div>)}
}
Person/index.jsx
import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
import { nanoid } from 'nanoid'
import { createAddPersonAction } from '../../redux/action/person'class Person extends Component {addPerson = ()=>{const name = this.nameNode.valueconst age = this.ageNode.valueconst personObj = {id:nanoid(),name:name,age:age}console.log(personObj)this.props.addPerson(personObj)this.nameNode.value = ''this.ageNode.value = ''}render() {return (<div><h2>我是Person组件,上方组件汇总为:{this.props.count}</h2><input ref={c=>this.nameNode = c} type='text' placeholder='输入名字'/><input ref={c=>this.ageNode = c} type='text' placeholder='输入年龄'/><button onClick={this.addPerson}>添加</button><ul>{this.props.persons.map((p)=>{return <li key={p.id}>{p.name}---{p.age}</li>})}</ul></div>)}
}// 创建并暴露一个的容器组件
export default connect(state => ({persons:state.person_array,count:state.count}),{addPerson:createAddPersonAction}
)(Person)
Count/index.jsx
import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/action/count'// 定义UI组件
class Count extends Component {// 加法increment = ()=>{const {value} = this.selectNumberthis.props.jia(value*1)}// 减法decrement = ()=>{const {value} = this.selectNumberthis.props.jian(value*1)}// 奇数再加incrementIfOdd = ()=>{const {value} = this.selectNumberif(this.props.count % 2 !== 0){this.props.jia(value*1)}}// 异步加incrementAsync = ()=>{const {value} = this.selectNumberthis.props.jiaAsync(value*1,500)}render() {return (<div><h2>我是Count组件</h2><h4>当前求和为:{this.props.count},下方组件总人数为:{this.props.person_count}</h4><select ref={c => this.selectNumber = c}><option value="1">1</option><option value="2">2</option><option value="3">3</option></select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button></div>)}
}// 创建并暴露一个Count的容器组件
export default connect(state => ({count:state.count,person_count:state.person_array.length}),{jia:createIncrementAction,jian:createDecrementAction,jiaAsync:createIncrementAsyncAction,}
)(Count)
redux/store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware,combineReducers } from "redux"
//引入为Count组件服务的reducer
import countReducer from './reducer/count'// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"
import personReducer from "./reducer/person"//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({count:countReducer,person_array:personReducer,
})//暴露store,注册异步action的异步中间件
export default createStore(allReducer,applyMiddleware(thunk))
redux/constant.js
/*该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词*/
export const INCREAMENT = 'increment'
export const DECREAMENT = 'decrement'
export const ADDPPERSON = 'add_person'
redux/action/count.js
/* 该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "../constant"//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})//异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data,time) => {return (dispatch)=>{setTimeout(()=>{// 通知redux加上datadispatch(createIncrementAction(data))},time)}
}
redux/action/person.js
import { ADDPPERSON } from "../constant"// 创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({type:ADDPPERSON,data:personObj})
redux/reducer/person.js
import {ADDPPERSON} from '../constant'// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]export default function personReducer(preState=initState,action){const {type,data} = actionswitch(type){case ADDPPERSON:return [data,...preState]default:return preState}
}
redux/reducer/count.js
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "../constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){//从action对象中获取:type、dataconst {type,data} = action//根据type决定如何加工数据switch (type){case INCREAMENT:// 加法return preState + datacase DECREAMENT:// 减法return preState - datadefault:return preState}
}
7.纯函数
7.1 reducer注意事项
- 如果使用了unshift:不是纯函数(不得改写参数数据)
import {ADDPPERSON} from '../constant'// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]export default function personReducer(preState=initState,action){const {type,data} = actionswitch(type){case ADDPPERSON:// 注意:redux对比的preState的对象内存地址,如果相同,那么就不会做更新。// 错误案例(不会更新):return preState.unshift(data)return [data,...preState]default:return preState}
}
7.2 纯函数
- 一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 1)不得改写参数数据
- 2)不会产生任何副作用,例如网络请求,输入和输出设备
- 3)不能调用 Date.now()或者Math.random()等不纯的方法
- redux的reducer 函数必须是一个纯函数
// 不是纯函数(只要是同样的输入(实参),必定得到同样的输出(返回))
function demo(a){return Math.random() + a
}
// 不是纯函数(不得改写参数数据)
function demo(a){a = 9return a
}// 纯函数
function demo(a){return a * 2
}
8.redux开发者工具
8.1 安装扩展程序
8.2 安装库
npm add redux-devtools-extension
8.3 store.js注册插件
- composeWithDevTools
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware,combineReducers } from "redux"
//引入为Count组件服务的reducer
import countReducer from './reducer/count'// 引入redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension"// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"
import personReducer from "./reducer/person"//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({count:countReducer,person_array:personReducer,
})//暴露store,注册异步action的异步中间件
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
十、react打包
- 将react代码转换为js代码,可以供浏览器识别
npm run build
十一、react扩展
1.setState
- setState式异步的,调用之后,不会马上更新值
- 总结:
- 1.对象式的setState是函数式的setState的简写方式(语法糖)
- 2.使用原则:
- (1).如果新状态不依赖于原状态===>使用对象方式
- (2).如果新状态依赖于原状态===>使用函数方式
- (3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
1.1 对象式
- setState(stateChange, [callback])------对象式的setState
- 1.statechange为状态改变对象(该对象可以体现出状态的更改)
- 2.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用
import React, { Component } from 'react'export default class Demo extends Component {state = {count:0}add = () => {const {count} = this.statethis.setState({count:count+1},()=>{//改完状态,render之后的值,是加1之后的值console.log("callback:" + this.state.count)})// 输出0console.log("输出;" + this.state.count)}render() {return (<div><h1>当前求和为:{this.state.count}</h1><button onClick={this.add}>点我+1</button></div>)}
}
1.2 函数式
- setState(updater, [callback])------函数式的setState
- 1.updater为返回statechange对象的函数。
- 2.updater可以接收到state和props。
- 3.callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。
import React, { Component } from 'react'export default class Demo extends Component {state = {count:0}add = () => {this.setState((state,props)=>{return {count:state.count+1}},()=>{//改完状态,render之后的值,是加1之后的值console.log("callback:" + this.state.count)})}render() {return (<div><h1>当前求和为:{this.state.count}</h1><button onClick={this.add}>点我+1</button></div>)}
}
2.路由懒加载(lazyLoad)
2.1 无懒加载
import React, { Component } from 'react'
import { NavLink,Route } from 'react-router-dom'
import About from './About'
import Home from './Home'export default class Demo extends Component {render() {return (<div><div><NavLink to="/home">Home</NavLink><hr/><NavLink to="/about">About</NavLink></div><div><Route path="/about" component={About}/><Route path="/home" component={Home}/></div></div>)}
}
2.2 懒加载
- 需要使用Suspense,并在callback中执行正在加载路由资源的时候显示的页面
import React, { Component, lazy,Suspense } from 'react'
import { NavLink,Route } from 'react-router-dom'const Home = lazy(()=> import('./Home'))
const About = lazy(()=> import('./About'))export default class Demo extends Component {render() {return (<div><div><NavLink to="/home">Home</NavLink><hr/><NavLink to="/about">About</NavLink></div><div><Suspense fallback={<h1>Loadind....</h1>}><Route path="/about" component={About}/><Route path="/home" component={Home}/></Suspense></div></div>)}
}
3.Hooks
3.1 是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用state 以及其他的React 特性
3.2 三个常用的Hook
- State Hook: React.useState()
- 状态钩子
- Effect Hook: React.useEffect()
- 生命周期钩子
- Ref Hook: React.useRef()
3.3 State Hook
- state Hook让函数组件也可以有state状态,并进行状态数据的读写操作
- 语法:const [xxx,setXxx]=React.useState(initValue)
- useState()说明:
- 参数:第一次初始化指定的值在内部作缓存
- 返回值:包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
- setXxx()2种写法:
- setxxx(newvalue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
- setxxx(value => newvalue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
import React from 'react'// 类式组件
// export default class Demo extends React.Component {// state = {count:0} // add = ()=>{
// this.setState({count:this.state.count+1})
// }// render() {
// return (
// <div>
// <h2>当前求和状态为{this.state.count}</h2>
// <button onClick={this.add}>点我+1</button>
// </div>
// )
// }
// }export default function Demo(){const [count,setCount] = React.useState(0)function add(){// 第一种写法// setCount(count+1)// 第二种写法setCount((count)=>{return count+1})}return (<div><h2>当前求和状态为{count}</h2><button onClick={add}>点我+1</button></div>)
}
3.4 Effect Hook
- Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
- React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅/启动定时器
- 手动更改真实DOM
- 语法和说明:
useEffect(() =>{//在此可以执行任何带副作用操作return()=>{//在组件卸载前执行//在此做一些收尾工作,比如清除定时器/取消订阅等}},[statevalue])//如果指定的是[],回调函数只会在第一次render()后执行
- 可以把 useEffect Hook看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
import React from 'react'
import ReactDOM from 'react-dom'// 类式组件
// export default class Demo extends React.Component {// state = {count:0} // add = ()=>{
// this.setState({count:this.state.count+1})
// }// unmount = ()=>{
// ReactDOM.unmountComponentAtNode(document.getElementById('root'))
// }// componentDidMount(){
// this.timer = setInterval(()=>{
// this.setState({count:this.state.count+1})
// },1000)
// }// componentWillUnmount(){
// clearInterval(this.timer)
// }// render() {
// return (
// <div>
// <h2>当前求和状态为{this.state.count}</h2>
// <button onClick={this.add}>点我+1</button>
// <button onClick={this.unmount}>卸载组件</button>
// </div>
// )
// }
// }export default function Demo(){const [count,setCount] = React.useState(0)React.useEffect(()=>{let timer = setInterval(()=>{setCount(count => count+1)},1000)return ()=>{clearInterval(timer)}},[])function add(){setCount(count+1)}function unmount(){ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (<div><h2>当前求和状态为{count}</h2><button onClick={add}>点我+1</button><button onClick={unmount}>卸载组件</button></div>)
}
3.5 Ref Hook
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法:const refcontainer =useRef()
- 作用:保存标签对象,功能与React.createRef()一样
import React from 'react'
import ReactDOM from 'react-dom'// 类式组件
// export default class Demo extends React.Component {// myRef = React.createRef()// show = ()=>{
// alert(this.myRef.current.value)
// }// render() {
// return (
// <div>
// <input type='text' ref={this.myRef}/>
// <button onClick={this.show}>点击提示数据</button>
// </div>
// )
// }
// }export default function Demo(){const myRef = React.useRef()function show(){alert(myRef.current.value)}function unmount(){ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (<div><input type='text' ref={myRef}/><button onClick={show}>点击提示数据</button></div>)
}
4.Fragment
- 作用:可以不用必须有一个真实的DOM根标签了
下面两种都可以:
<></>
import React from 'react'
import ReactDOM from 'react-dom'export default class Demo extends React.Component {myRef = React.createRef()show = ()=>{alert(this.myRef.current.value)}render() {return (<><input type='text' ref={this.myRef}/><button onClick={this.show}>点击提示数据</button></>)}
}
5.Context
- 一种组件间通信方式,常用于【祖组件】与【后代组件】间通信
5.1 使用
// 1.创建Context容器对象:
const xxxContext = React.createContext()// 2.渲染子组的时候,外面包裹一层xxxContext.Provider,通过value属性给后代组件传递数据:
<xxxContext.provider value={数据}>
子组件
</xxxContext.Provider>// 3.后代组件读取数据:
// 3.1 第一种方式:仅适用于类组件
static contextType = xxxContext //声明接收context
this.context // 读取context中value的数据
// 3.2 第二种方式:函数组件与类组件都可以
<xxxContext.Consumer>
{value => ( //value就是context中的value数据要显示的内容)
}
</xxxContext.Consumer>
5.2 类组件代码示例
import React, { Component } from 'react'// 创建Context对象
const MyContext = React.createContext()export default class A extends Component {state = {username:'tom',age:18}render() {const {username,age} = this.statereturn (<div><h3>我是A组件</h3><h4>我的用户名是:{username}</h4><MyContext.Provider value={{username,age}}><B/></MyContext.Provider></div>)}
}class B extends Component {static contextType = MyContextrender() {return (<div><h3>我是B组件</h3><h4>我的从A组件接收到的用户名是:{this.context.username}</h4><C/></div>)}}class C extends Component {static contextType = MyContextrender() {return (<div><h3>我是C组件</h3><h4>我的从A组件接收到的用户名是:{this.context.username},年龄是:{this.context.age}</h4></div>)}}
5.3 函数组件代码示例
import React, { Component } from 'react'// 创建Context对象
const MyContext = React.createContext()export default class A extends Component {state = {username:'tom',age:18}render() {const {username,age} = this.statereturn (<div><h3>我是A组件</h3><h4>我的用户名是:{username}</h4><MyContext.Provider value={{username,age}}><B/></MyContext.Provider></div>)}
}class B extends Component {static contextType = MyContextrender() {return (<div><h3>我是B组件</h3><h4>我的从A组件接收到的用户名是:{this.context.username}</h4><C/></div>)}}function C(){return (<div><h3>我是C组件</h3><h4>我的从A组件接收到的用户名是:<MyContext.Consumer>{value => {return `${value.username},年龄是:${value.age}`}}</MyContext.Consumer></h4></div>)
}
6.组件优化
- Component的两个问题:
- 只要执行setState(),即使不改变状态数据(this.setState({})),组件也会重新render()
- 只当前组件重新render(),就会自动重新render子组件(尽管没有传任何东西给子组件,子组件也会重新render)==>效率低
- 效率高的做法:只有当组件的state或props数据发生改变时才重新render()
- 原因:Component中的shouldComponentUpdate()总是返回true
6.1 解决办法1
- 重写shouldComponentUpdate()方法
- 比较新旧state或props数据,如果有变化才返回true,如果没有返回false
import React, { Component } from 'react'export default class Parent extends Component {state = {carName:"奔驰c36"}changeCar = ()=>{this.setState({carName:'迈巴赫'})}shouldComponentUpdate(nextProps,nextState){if(this.state.carName === nextState.carName) return falseelse return true}render() {console.log('Parent')const {carName} = this.statereturn (<div><h3>我是Parent组件</h3><span>我的车的名字是:{carName}</span><button onClick={this.changeCar}>点击换车</button><Child carName="奥拓"/></div>)}
}class Child extends Component {shouldComponentUpdate(nextProps,nextState){if(this.props.carName === nextProps.carName) return falseelse return true}render() {console.log('Child')return (<div><h3>我是Child组件</h3><span>接收到的车:{this.props.carName}</span></div>)}
}
6.2 解决办法2(推荐)
- 使用PureComponent
- PureComponent重写了shouldcomponentUpdate(),只有state或props数据有变化才返回true
- 注意:
- 只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false
- 不要直接修改state数据,而是要产生新数据
import React, { PureComponent } from 'react'export default class Parent extends PureComponent {state = {carName:"奔驰c36"}changeCar = ()=>{this.setState({carName:'迈巴赫'})}render() {console.log('Parent')const {carName} = this.statereturn (<div><h3>我是Parent组件</h3><span>我的车的名字是:{carName}</span><button onClick={this.changeCar}>点击换车</button><Child carName="奥拓"/></div>)}
}class Child extends PureComponent {render() {console.log('Child')return (<div><h3>我是Child组件</h3><span>接收到的车:{this.props.carName}</span></div>)}
}
7.render props
7.1 如何向组件内部动态传入带内容的结构(标签)
Vue中:使用slot技术,也就是通过组件标签体传入结构<A><B/></A>
React中:使用children props:通过组件标签体传入结构使用render props:通过组件标签属性传入结构,一般用render函数属性
7.2 children props
<A><B>xxxx</B>
</A>
问题:如果B组件需要A组件内的数据,==>做不到
import React, { Component } from 'react'export default class Parent extends Component {render() {return (<div><h3>我是Parent组件</h3><A><B/></A> </div>)}
}class A extends Component {state = {name:'tom'}render() {return (<div><h3>我是A组件</h3>{this.props.children}</div>)}
}class B extends Component {render() {return (<div><h3>我是B组件</h3></div>)}
}
7.3 render props
<A render={(data) => <C data={data}></C>}></A>
A组件:{this.props.render(内部state数据)}
C组件:读取A组件传入的数据显示{this.props.data}
import React, { Component } from 'react'export default class Parent extends Component {render() {return (<div><h3>我是Parent组件</h3>{/* // 下面的写法,满足不了A传递参数给B<A><B/></A> */}<A render={(name)=><B name={name}/>}/></div>)}
}class A extends Component {state = {name:'tom'}render() {return (<div><h3>我是A组件</h3>{this.props.render(this.state.name)}</div>)}
}class B extends Component {render() {return (<div><h3>我是B组件</h3></div>)}
}
8.错误边界
- 理解:错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面。如果前端代码报错了,避免显示以下界面
- 特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
- 使用方式:getDerivedStateFromError配合componentDidCatch
Parent.jsx
import React, { Component } from 'react'
import Child from './Child'export default class Parent extends Component {state = {hasError:''}// 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息static getDerivedStateFromError(error){console.log(error)return {hasError:error}}// 子组件渲染时出错,就会调用该方法。componentDidCatch(){// 可以统计此处的错误,然后发送给后台记录,用于通知编码人员进行bug解决}render() {return (<div><h2>我是Parent组件</h2>{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}</div>)}
}
Child.jsx
- 子组件
import React, { Component } from 'react'export default class Child extends Component {state = ''render() {return (<div><h2>我是Child件</h2>{this.state.users.map((user)=>{return <h4 key={user.id}>{user.name}---{user.age}</h4>})}</div>)}
}
9.组件通信方式总结
- 组件间关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
- 通信方式:
- 1.props:
- children props
- render props
- 2.消息订阅-发布:
- pubs-sub、event等等
- 3.集中式管理:
- redux、dva等等
- 4.conText:
- 生产者-消费者模式
- 1.props:
- 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
十二、React Router6
1.概述
- 1.React Router以三个不同的包发布到 npm上,它们分别为:
- 1.1 react-router:路由的核心库,提供了很多的:组件、钩子。
- 1.2
react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如<BrowserRouter\>等
- 1.3 react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:
<NativeRouter>
等。
- 2.与React Router 5.x版本相比,改变了什么?
- 2.1 内置组件的变化:移除<switch/>新增<Routes/>等。
- 2.2 语法的变化:
component={About}
变为element={<About/>}
等。 - 2.3 新增了多个hook:
useParams
、useNavigate
、useMatch
等 - 2.4 官方推荐函数式组件
2.component
2.1 BrowerRouter
- 改组件用于包裹整个应用
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";ReactDOM.render(<BrowserRouter>{/* 整体结构(通常为App组件) */}</BrowserRouter>,root
);
2.2 HashRouter
- 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。
- 备注:6.x版本中<HashRouter>、<BrowserRouter>的用法与5.x相同。
2.3 Routes与Route
- v6版本中移出了先前的<switch>,引入了新的替代者:<Routes>
- <Route>和<Routes>要配合使用,且必须要用<Routes>包裹<Route>。
- <Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件。
- <Route caseSensitive>属性用于指定:匹配时是否区分大小写(默认为false)。
- 当URL发生变化时,<Routes>都会查看其所有子<Route>元素以找到最佳匹配并呈现组件。
- <Route>也可以嵌套使用,且可配合 useRoutes()配置“路由表”,但需要通过<Outlet>组件来渲染其子路由。
<Routes>/*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/<Route path="/login" element={<Login />}></Route>/*用于定义嵌套路由,home是一级路由,对应的路径/home*/<Route path="home" element={<Home />}>/*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/<Route path="test1" element={<Test/>}></Route><Route path="test2" element={<Test2/>}></Route></Route>//Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx<Route path="users"><Route path="xxx" element={<Demo />} /></Route>
</Routes>
2.4 Link
- 作用:修改URL,且不发送网络请求(路由链接)。
- 注意:外侧需要用<BrowserRouter>或<HashRouter>包裹。
import { Link } from "react-router-dom";function Test() {return (<div><Link to="/路径">按钮</Link></div>);
}
2.5 NavLink
- 作用:与<Link>组件类似,且可实现导航的“高亮”效果。
// 注意: NavLink默认类名是active,下面是指定自定义的class//自定义样式
<NavLinkto="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>
2.6 Navigate
- 作用:只要
<Navigate>
组件被渲染,就会修改路径,切换视图。 replace
属性用于控制跳转模式(push 或 replace,默认是push)。
import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'export default function Home() {const [sum,setSum] = useState(1)return (<div><h3>我是Home的内容</h3>{/* 根据sum的值决定是否切换视图 */}{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}<button onClick={()=>setSum(2)}>点我将sum变为2</button></div>)
}
2.7 Outlet
- 当
<Route>
产生嵌套时,渲染其对应的后续子路由。
//根据路由表生成对应的路由规则
const element = useRoutes([{path:'/about',element:<About/>},{path:'/home',element:<Home/>,children:[{path:'news',element:<News/>},{path:'message',element:<Message/>,}]}
])//Home.js
import React from 'react'
import {NavLink,Outlet} from 'react-router-dom'export default function Home() {return (<div><h2>Home组件内容</h2><div><ul className="nav nav-tabs"><li><NavLink className="list-group-item" to="news">News</NavLink></li><li><NavLink className="list-group-item" to="message">Message</NavLink></li></ul>{/* 指定路由组件呈现的位置 */}<Outlet /></div></div>)
}
3.Hooks
3.1 useRoutes()
- 作用:根据路由表,动态创建
<Routes>
和<Route>
。
//路由表配置:src/routes/index.js
import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'export default [{path:'/about',element:<About/>},{path:'/home',element:<Home/>},{path:'/',element:<Navigate to="/about"/>}
]//App.jsx
import React from 'react'
import {NavLink,useRoutes} from 'react-router-dom'
import routes from './routes'export default function App() {//根据路由表生成对应的路由规则const element = useRoutes(routes)return (<div>......{/* 注册路由 */}{element}......</div>)
}
3.2 useNavigate()
- 作用:返回一个函数用来实现编程式导航。
import React from 'react'
import {useNavigate} from 'react-router-dom'export default function Demo() {const navigate = useNavigate()const handle = () => {//第一种使用方式:指定具体的路径navigate('/login', {replace: false,state: {a:1, b:2}}) //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法navigate(-1)}return (<div><button onClick={handle}>按钮</button></div>)
}
3.3 useParams()
- 作用:回当前匹配路由的
params
参数,类似于5.x中的match.params
。
import React from 'react';
import { Routes, Route, useParams } from 'react-router-dom';
import User from './pages/User.jsx'function ProfilePage() {// 获取URL中携带过来的params参数let { id } = useParams();
}function App() {return (<Routes><Route path="users/:id" element={<User />}/></Routes>);
}
3.4 useSearchParams()
- 作用:用于读取和修改当前位置的 URL 中的查询字符串。
- 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
import React from 'react'
import {useSearchParams} from 'react-router-dom'export default function Detail() {const [search,setSearch] = useSearchParams()const id = search.get('id')const title = search.get('title')const content = search.get('content')return (<ul><li><button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button></li><li>消息编号:{id}</li><li>消息标题:{title}</li><li>消息内容:{content}</li></ul>)
}
3.5 useLocation()
- 作用:获取当前 location 信息,对标5.x中的路由组件的
location
属性。
import React from 'react'
import {useLocation} from 'react-router-dom'export default function Detail() {const x = useLocation()console.log('@',x)// x就是location对象: /*{hash: "",key: "ah9nv6sz",pathname: "/login",search: "?name=zs&age=18",state: {a: 1, b: 2}}*/return (<ul><li>消息编号:{id}</li><li>消息标题:{title}</li><li>消息内容:{content}</li></ul>)
}
3.6 useMatch()
- 作用:返回当前匹配信息,对标5.x中的路由组件的
match
属性。
<Route path="/login/:page/:pageSize" element={<Login />}/>
<NavLink to="/login/1/10">登录</NavLink>export default function Login() {const match = useMatch('/login/:x/:y')console.log(match) //输出match对象//match对象内容如下:/*{params: {x: '1', y: '10'}pathname: "/LoGin/1/10" pathnameBase: "/LoGin/1/10"pattern: {path: '/login/:x/:y', caseSensitive: false, end: false}}*/return (<div><h1>Login</h1></div>)
}
3.7 useInRouterContext()
- 作用:如果组件在
<Router>
的上下文中呈现,则useInRouterContext
钩子返回 true,否则返回 false。 - 场景:有判断使用人是不是在路由组件使用了我封装的组件
3.8 useNavigationType()
- 作用:返回当前的导航类型(用户是如何来到当前页面的)。
- 返回值:
POP
、PUSH
、REPLACE
。 - 备注:
POP
是指在浏览器中直接打开了这个路由组件(刷新页面)。
3.9 useOutlet()
- 作用:用来呈现当前组件中渲染的嵌套路由。
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象
3.10 useResolvedPath()
- 作用:给定一个 URL值,解析其中的:path、search、hash值。
import {useResolvedPath} from 'react-router-dom
const pathObj = useResolvedPath('/user?id=001&name=tom#qwe')
// 解析出来是:{pathname:'/user', search: '?id=001&name=tom', hash: '#qwe'}
4.综合案例
4.1 useRouters路由表
- 一般来说,路由都会放到src/routes里面
4.2 嵌套路由
路由表
一级路由
二级路由
4.3 路由传参
Link参数定义
接收参数
- params传参
- search传参
- state参数
4.4 编程式路由导航
相关文章:

React基础大全
文章目录 一、React基本介绍1.虚拟DOM优化1.1 原生JS渲染页面1.2 React渲染页面 2.需要提前掌握的JS知识 二、入门1.React基本使用2.创建DOM的两种方式2.1 使用js创建(一般不用)2.2 使用jsx创建 3.React JSX3.1 JSX常见语法规则3.2 for循环渲染数据 4.模…...

51c大模型~合集10
我自己的原文哦~ https://blog.51cto.com/whaosoft/11547799 #Llama 3.1 美国太平洋时间 7 月 23 日,Meta 公司发布了其最新的 AI 模型 Llama 3.1,这是一个里程碑时刻。Llama 3.1 的发布让我们看到了开源 LLM 有与闭源 LLM 一较高下的能力。 Meta 表…...

【已解决】element-plus配置主题色后,sass兼容问题。set-color-mix-level() is...in Dart Sass 3
项目:vue3vite "scripts": {"dev": "vite","build": "vite build","preview": "vite preview"},"dependencies": {"element-plus/icons-vue": "^2.3.1",&quo…...

JavaWeb——Web入门(4/9)-HTTP协议:请求协议(请求行、请求头、请求体、演示 )
目录 请求协议概述 请求行 请求头 请求体 演示 GET POST 请求协议概述 介绍完了 HTTP 协议的概念以及特点之后,接下来介绍 HTTP 当中的请求协议。 请求协议指的就是请求数据的格式。 HTTP 请求协议在整个 Web 通信中起着至关重要的作用。当用户在浏览器…...
软考:数据库考点总结
结构冲突 在数据库领域,冲突主要指的是在并发操作中,多个事务试图同时访问或修改相同的数据资源,导致数据一致性、完整性和隔离性受到威胁。以下是数据库中常见的几种冲突类型: 读写冲突(Read-Write Conflict…...

Flash的语音ic型号有哪些?
深圳唯创知音电子有限公司在语音技术领域具有深厚的积累,其Flash语音IC产品凭借高性能和广泛的应用领域,在市场上占据了一席之地。以下是对该公司Flash语音IC产品的详细介绍: 一、产品概述 Flash语音IC是一种采用Flash存储技术的语音芯片&…...

10天进阶webpack---(1)为什么要有webpack
首先就是我们的代码是运行在浏览器上的,但是我们开发大多都是利用node进行开发的,在浏览器中并没有node提供的那些环境。这就早成了运行和开发上的不同步问题。 -----引言 浏览器模块化的问题: 效率问题:精细的模块划分带来了更…...

HTML CSS
目录 1. 什么是HTML 2. 什么是CSS ? 3. 基础标签 & 样式 3.1 新浪新闻-标题实现 3.1.1 标题排版 3.1.1.1 分析 3.1.1.2 标签 3.1.1.3 实现 3.1.2 标题样式 3.1.2.1 CSS引入方式 3.1.2.2 颜色表示 3.1.2.3 标题字体颜色 3.1.2.4 CSS选择器 3.1.2.5 发布时间字…...

第03章 MySQL的简单使用命令
一、MySQL的登录 1.1 服务的启动与停止 MySQL安装完毕之后,需要启动服务器进程,不然客户端无法连接数据库。 在前面的配置过程中,已经将MySQL安装为Windows服务,并且勾选当Windows启动、停止时,MySQL也 自动启动、停止…...

【C++动态规划】2435. 矩阵中和能被 K 整除的路径|1951
本文涉及知识点 C动态规划 LeetCode2435. 矩阵中和能被 K 整除的路径 给你一个下标从 0 开始的 m x n 整数矩阵 grid 和一个整数 k 。你从起点 (0, 0) 出发,每一步只能往 下 或者往 右 ,你想要到达终点 (m - 1, n - 1) 。 请你返回路径和能被 k 整除的…...

三、Kafka集群
一、Kafka集群的概念 1、目的 高并发、高可用、动态扩展。 主备数据架构、双活节点、灾备数据中心。 如果是服务的地理范围过大也可以使不同的集群节点服务不同的区域,降低网络延迟。 2、Kafka集群的基本概念 1)复制(镜像) kaf…...
[数据结构]堆
堆,本质是一颗完全二叉树。属于非线性结构。 代码实现可参考树的代码。 函数介绍: //此堆是小堆,大堆操作部分与小堆相反 void InitHeap(Heap* cat) {assert(cat);cat->arr NULL;cat->capacity cat->size 0; } void DestroyHeap(Heap* cat) {assert(…...

UDP-鼠李糖合成酶基因的克隆与鉴定-文献精读76
何首乌中UDP-鼠李糖合成酶基因FmRHM1/2的克隆与鉴定 摘要 UDP-鼠李糖是一种由UDP-鼠李糖合酶(RHM)催化合成的鼠李糖供体,而鼠李糖是鼠李糖苷化合物的重要组成部分,植物中只有少数基因编码的酶参与UDP-鼠李糖生物合成。本研究基于…...
【H2O2|全栈】JS进阶知识(四)Ajax
目录 前言 开篇语 准备工作 基本概念 原生JS使用AJAX 创建AJAX对象 设置请求方式和地址 设置请求头 发送请求 get方式发送 post方式发送 获取响应数据 AJAX状态码和HTTP状态消息 错误捕获 原生JS封装AJAX方法 $ 调用AJAX方法 结束语 前言 开篇语 本系列博客…...

Spring IOC的工作流程
Spring IOC的工作流程 好的,这个问题我会从几个方面来回答。 IOC是什么 Bean的声明方式 IOC的工作流程 IOC的全称是 Inversion Of Control,也就是控制反转,它的核心思想是把对象的管理权限交给容器。(展示图 1) &…...

从新手到专家:7款电脑平面设计软件评测
平面设计在时尚、广告等多个领域扮演着重要角色,而创作出独特且富有创意的设计作品则需要依赖优秀的电脑平面设计软件。市场上的电脑平面设计软件众多,每款软件都有其独到之处。本文将为你推荐几款值得关注的电脑平面设计软件,并分析它们的特…...

【C++】如何让C++字符串更快、C++的小字符串优化
二十三、如何让C字符串更快、C的小字符串优化 1、如何让C字符串更快? 如果程序中有很多字符串操作,比如格式化文本(日志记录),那是非常糟糕的,因为字符串操作是很慢的。字符串string和它相关的很多函数很可能会自动分配内存&…...

C++《list》
在本篇当中我们将学习STL中的list,在此list就是我们之前在数据结构学习过的链表,在本篇中我们要来了解list当中的成员函数该如何使用,由于list各个函数的接口和之前学习过的vector类型,因此在学习list的使用就较为轻松。在lis篇章…...
strongswan中METHOD定义
strongswan中使用METHOD来定义函数(方法),如下get_first函数定义。 METHOD(linked_list_t, get_first, status_t,private_linked_list_t *this, void **item) {if (this->count 0)return NOT_FOUND;*item this->first->value;ret…...

Rive 动画框架竟然支持响应式布局,全平台动画框架开启全新 UI 交互能力
没用过 Rive 的可能对于 Rive 还不熟悉,其实之前已经介绍过 Rive 好几次,例如《Rive 2 动画库「完全商业化」》 和《给掘金 Logo 快速添加动画效果》 等文章都介绍过 Rive ,之所以会接触 Rive 到, 也是因为多年前想在 Flutter 平台…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...