文章目录
- 一、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.创建虚拟DOM
const 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,所以会打印undefined
console.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.state
return <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.state
return <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.props
return (
<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.props
return (
<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} = props
return (
<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.refs
alert(input1.value)
}
render(){
const {name,sex,age} = this.props
return (
<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.props
return (
<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.props
return (
<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.props
return (
<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.props
return (
<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} = this
alert(`你的用户名是:${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.state
alert(`你的用户名是:${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.state
alert(`你的用户名是:${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.state
alert(`你的用户名是:${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.1
opacity -= 0.1
if(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" />
<!-- 描述信息,搜索引擎收录我们网页信息的时候有用 -->
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 用于指定网页添加到手机主屏幕快捷方式的图标(苹果手机专用) -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- 应用加壳时的配置文件 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<!-- 若浏览器不支持js,则展示标签中的内容 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- 总入口容器 -->
<div id="root"></div>
</body>
</html>
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:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为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}} = this
console.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.state
return (
<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.params
return (
<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.state
return (
<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.location
const {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.state
return (
<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.state
return (
<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.state
return (
<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.selectNumber
const {count} = this.state
this.setState({"count":count+value*1})
}
// 减法
decrement = ()=>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({"count":count-value*1})
}
// 奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const {count} = this.state
if(count%2 !== 0){
this.setState({"count":count+value*1})
}
}
// 异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
const {count} = this.state
setTimeout(()=>{
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、data
const {type,data} = action
//根据type决定如何加工数据
switch (type){
case 'increment':
// 加法
return preState + data
case 'decrement':
// 减法
return preState - data
default:
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.selectNumber
store.dispatch(createIncrementAction(value*1))
}
// 减法
decrement = ()=>{
const {value} = this.selectNumber
store.dispatch(createDecrementAction(value*1))
}
// 奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const count = store.getState()
if(count%2 !== 0){
store.dispatch(createIncrementAction(value*1))
}
}
// 异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
const 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、data
const {type,data} = action
//根据type决定如何加工数据
switch (type){
case INCREAMENT:
// 加法
return preState + data
case DECREAMENT:
// 减法
return preState - data
default:
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加上data
dispatch(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加上data
dispatch(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、data
const {type,data} = action
//根据type决定如何加工数据
switch (type){
case INCREAMENT:
// 加法
return preState + data
case DECREAMENT:
// 减法
return preState - data
default:
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的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
// 等于:<CountUI n={900}/>
return {count:state}
}
/* 映射操作状态得方法
1.mapDispatchToProps函数返回是一个对象
2.返回得对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.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.selectNumber
this.props.jia(value*1)
}
// 减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
// 奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
// 异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.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.selectNumber
this.props.jia(value*1)
}
// 减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
// 奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
// 异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.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.value
const age = this.ageNode.value
const 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.selectNumber
this.props.jia(value*1)
}
// 减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
// 奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
// 异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.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加上data
dispatch(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} = action
switch(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、data
const {type,data} = action
//根据type决定如何加工数据
switch (type){
case INCREAMENT:
// 加法
return preState + data
case DECREAMENT:
// 减法
return preState - data
default:
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} = action
switch(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 = 9
return 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.state
this.setState({count:count+1},()=>{
//改完状态,render之后的值,是加1之后的值
console.log("callback:" + this.state.count)
})
// 输出0
console.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.state
return (
<div>
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<MyContext.Provider value={{username,age}}>
<B/>
</MyContext.Provider>
</div>
)
}
}
class B extends Component {
static contextType = MyContext
render() {
return (
<div>
<h3>我是B组件</h3>
<h4>我的从A组件接收到的用户名是:{this.context.username}</h4>
<C/>
</div>
)
}
}
class C extends Component {
static contextType = MyContext
render() {
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.state
return (
<div>
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<MyContext.Provider value={{username,age}}>
<B/>
</MyContext.Provider>
</div>
)
}
}
class B extends Component {
static contextType = MyContext
render() {
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 false
else return true
}
render() {
console.log('Parent')
const {carName} = this.state
return (
<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 false
else 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.state
return (
<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
//自定义样式
<NavLink
to="login"
className={({ isActive }) => {
console.log('home', isActive)
return isActive ? 'base one' : 'base'
}}
>login</NavLink>
/*
默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
<NavLink to="home" end >home</NavLink>
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 编程式路由导航