React由Facebook(现在叫 meta) 的软件工程师Jordan Walke创建。2013年的时候在社区开源。那么react是什么呢?React是一个把数据渲染为HTML视图的开源JavaScript 库 [ 视图层框架 ] 。React为程序员提供了一种子组件不能直接影响外层组件的模型 [ 单向数据流 ],数据改变时会对HTML文档进行有效的更新。
特点:
react脚手架
、基于webpack 构建 react 工程化环境 简称 cra
npm i create-react-app -g
create-react-app 项目名 // 创建项目
xml in js
<student>
<name>小明</name>
<age>18</age>
<gender>男</gender>
</student>
react中的jsx, 可以在js中直接 写标签,react会自动转换成 虚拟dom对象结构
数据 驱动框架,一定都会设计 虚拟dom, 是虚拟dom 编写 过于繁琐,vue解决方案设计 sfc 在template标签直接 写 标签,自己自动编译成虚拟dom
react解决方案 在js代码直接 写标签, react会自动将 js标签 编译成虚拟dom
洗脑洗脑洗脑洗脑:
react中 在 js看到任意标签, 理解为这是对象对象对象对象
const obj = {
id: 'box',
className: 'wrap'
}
<div {...obj}>1111</div>
注意
所有组件 首字母必须大写
文件 后缀名建议 为 jsx
现在是 趋势
import React from "react";
// react函数式组件
const App = (props) => {
console.log(props);
return (
<div>
<h2>这是函数式组件</h2>
</div>
)
}
export default App
<App title="主标题" subTitle="副标题"/>
// 相当于调用函数
ES7+ React/Redux/React-Native snippets
定义属性 两种方式
定义方法两种方式
继承
静态属性静态方法
import React, { Component } from 'react';
class App extends Component{
// render方法渲染 虚拟dom 方法 必须有返回值
render(){
console.log(this);
return (
<div>
<h2>这是react的class组件</h2>
{
this.props.title
}
<br />
{
this.props.subTitle
}
</div>
)
}
}
<App title="主标题" subTitle="副标题"/>
// 相当于 new 类 实例调用render 传递 自定义属性 挂载实例的 props属性上
类组件和 函数式组件的不同点:
原理:
React 包有一个方法 createElement(类似于vue中的h渲染函数), react会自动分析 jsx 标签的结构,并自动 调用 React.createElement 方法 将 写jsx标签编译成 虚拟dom对象
React.createElement('div', {
id: 'box',
className: 'aaa'
},[
React.createElement('p', {className:'op'}, ['这是p']),
React.createElement('span', {className:'span'}, ['这是span']),
'这是文本内容'
])
<div style={
{
width: 100,
height: 100+200,
backgroundColor: 'red'
}
}></div>
引入外部的css
使用 css预处理器 sass
cra 内置 sass 环境
npm i sass -D
注意:
不管是 css还是外部 sass文件,都没有做任何 作用域限制,作用到全局所有的标签
选择器编写不要 污染
CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题。也就是说:所有类名都具有“局部作用域”,只在当前组件内部生效。在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM。
/*app.module.css*/
.box{
width: 200px;
height: 200px;
background-color: #d4ac33;
}
.box2{
width: 200px;
height: 200px;
background-color: #2724d5;
}
import styles from './app.module.css'
/*
{
box: '编译后类名',
box2: '编译后类名'
}
*/
<div className={styles.box}></div>
<div className={styles.box2}></div>
scss 使用 css module
.box{
width: 200px;
height: 200px;
background-color: #cb6d6d;
.a{
width: 100px;
height: 100px;
background-color: #c6d0af;
}
}
import styles from './xxx.module.scss'
// 编译成
{
box: '编译后类名',
a: '编译后类名'
}
<div className={styles.box}>
<div className={styles.a}></div>
</div>
scss 嵌套规则 编译成平行
定义:global即可嵌套
.box2{
width: 200px;
height: 200px;
background-color: #cb6d6d;
:global{
.a{
width: 100px;
height: 100px;
background-color: #c6d0af;
.b{
color: red;
}
}
}
}
使用时
<div className={styles.box2}>
<div className="a">
<div className="b">aaaa</div>
</div>
</div>
理念:万物皆组件
styled-components
原理:
通过样式组件 定义 样式和结构
npm i styled-components -S
import styled, {keyframes} from 'styled-components';
// 定义样式组件
/*
编译成一个组件 Box 内容就是 div标签,且具有如下的样式
*/
const Box = styled.div`
width: 200px;
height: 200px;
background: red;
`
// 内容和选择器的嵌套
const Box2 = styled.div`
width: 200px;
height: 200px;
background: red;
p{
color: blue;
}
span{
color: green;
&:hover{
color: yellow;
}
}
`
// 继承
const Box3 = styled(Box)`
border: 5px solid #11ee56;
`
// 传递props
const Box4 = styled.div`
width: 200px;
height: 200px;
background: ${props => props.bgc?props.bgc:'#dc6666' };
`;
// 定义关键帧
const move = keyframes`
0% {
transform: rotate(45deg);
}
100% {
transform: rotate(-45deg);
}
`
const Box5 = styled.div`
width: 10px;
height: 200px;
background: red;
margin: 50px auto;
transform-origin: center bottom;
animation: ${move} 200ms infinite alternate linear;
`
export {
Box,
Box2,
Box3,
Box4,
Box5
}
npm i prop-types -S
// 1 引入 PropTypes
import PropTypes from 'prop-types'
class Todo extends Component{
}
// 或者 函数式组件
const Todo = (props) => {
}
// 定义函数 静态属性
Todo.propTypes = {
// 一下是常用类型校验
a: PropTypes.array,
b: PropTypes.bigint,
c: PropTypes.bool,
d: PropTypes.func,
e: PropTypes.number,
f: PropTypes.object,
g: PropTypes.string,
h: PropTypes.symbol,
// 是react组件 传递 需要加标签 实例化后<Todo/> 使用时 {props.i}
i: PropTypes.element,
// 是react组件 传递 使用时 <props.j/>
j: PropTypes.elementType,
// 必须是 Message的实例
k: PropTypes.instanceOf(Message),
// 枚举 值 必须是给定 多个值中的一个
o: PropTypes.oneOf(['News', 'Photos']),
// 类型必须是 给定多个类型中的一个
p: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 固定类型的数组 比如 是 number数组
q: PropTypes.arrayOf(PropTypes.number),
// 对象 且属性值必须是固定类型
r: PropTypes.objectOf(PropTypes.number),
// 描述的对象结构, 对象 如下属性必须符合要求(对于其他属性没有做要求)
s: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 描述对象结构, 对象必须 只有 如下 属性且必须满足 类型要求
t: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 链式 调用 要求类型 且必传
u: PropTypes.func.isRequired,
}
直接定义组件静态属性 defaultProps 中定义默认值即可
class Todo extends Component {}
const Todo = (props) => {}
Todo.defaultProps = {}
类似于vue slot 实现,标签在使用时 父组件传递一些视图 模板给子组件
<Todo>
<div>
<h2>哈哈哈</h2>
<h3>嘿嘿嘿</h3>
</div>
</Todo>
const Todo = (props) => {
return (
<div>
{props.children}
</div>
)
}
注意:函数式组件没有内部状态管理
state管理组件 内部状态
语法:
直接定义 实例的state属性,在属性管理数据即可
class Todo extends Component {
// 外部直接定义
state = {
msg: '这是实例自己的状态'
};
// 在constructor中定义
constructor(){
super();
this.state = {
msg: '这是实例自己的状态',
isBeauty: true
}
};
}
修改state
注意:
react 不是mvvm框架, 不能直接修改state, 对于state 父类原型 提供了 两个方法让视图刷新
this.setState({
msg: '修改的值',
isBeauty: !this.state.isBeauty
})
2 传函数,函数返回值中修改 this.setState((state, props) => {
return {
msg: '这是修改后的值',
isBeauty: !state.isBeauty
}
})
问题?
setState修改数据后 会产生副作用(再次调用render,生成组件新的虚拟dom,比较新老dom更新真实dom), setState设计成了 异步的 提高代码执行效率
数据修改后,无法 直接获取 修改后的数据和视图的
如何获取修改后最新的数据和dom react 给setState设计了回调, 在数据修改后,且视图更新完成后触发
this.setState({}, () => {
// 在这里可以拿到修改后最新的数据和dom
})
on事件首字母大写
onClick onMouseover onMousedown
绑定合成事件语法
<button onClick={事件函数}>按钮</button>
注意:
事件函数不能加括号
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
() => {
this.setState({
isBeauty: !this.state.isBeauty
})
}
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
}
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
this.clickBtn.bind(this)
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn(){
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
this.clickBtn = this.clickBtn.bind(this);
};
render() {
return (
<div>
<button onClick={
this.clickBtn
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn(){
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
推荐写法
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
this.clickBtn
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn = () => {
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
事件函数的第一个参数就是事件对象
e.stopPropagation() // 取消冒泡
e.preventDefault() // 阻止默认事件
e.target // 获取事件源
行内定义箭头函数充当事件 函数, 方法在 箭头函数中调用即可传参
class Todo extends Component {
render() {
return (
<div>
<button onClick={
(e) => {
this.clickBtn(5, e)
}
}>按钮</button>
</div>
)
};
clickBtn = (n, e) => {
alert(n)
e.target.style.background = 'red';
}
}
class Todo extends Component {
state = {
isShow: true
}
render() {
return (
<div>
<button onClick={
() => {
this.setState({
isShow: !this.state.isShow
})
}
}>{this.state.isShow? '隐藏': '显示'}</button>
{
this.state.isShow
&&
<div className="box"></div>
}
{
this.state.isShow
?
<div className="box"/>
:
<div className="box2"/>
}
</div>
)
}
}
class Todo extends Component {
state = {
arr: ['a', 'b', 'c', 'd']
}
render() {
return (
<div>
<ul>
{
this.state.arr.map((item, index) => {
return (
<li key={index}>
{item}
{index}
</li>
)
})
}
</ul>
</div>
)
}
}
<div dangerouslySetInnerHTML={{__html: this.state.content}}></div>
import React, { Fragment } from 'react'
import Todo from './Todo'
export default function App() {
return (
<Fragment>
<Todo />
</Fragment>
)
}
也可以使用 空标签充当容器
class Todo extends Component {
render() {
return (
<>
<h2>这是todo</h2>
</>
)
}
}
绑定初始值 (表单控件 初始值 等于 某个 状态,值是可变,变化后不会 改变状态)
提供了两个属性分别是
defaultValue
defaultChecked
class Todo extends Component {
state = {
msg: '这是初始值',
isBeauty: true
}
render() {
return (
<>
<input type="text" defaultValue={this.state.msg}/>
<br />
{this.state.msg}
<hr />
<input type="checkbox" defaultChecked={this.state.isBeauty} />
<br />
{
this.state.isBeauty?'真的':'假的'
}
</>
)
}
}
双向绑定
原理:
直接将状态绑定 表单控件的 value和checked属性, 添加onChange 事件 在事件函数
e.target.value e.target.checked 赋值给 绑定state即可
class Todo extends Component {
state = {
msg: '这是初始值',
isBeauty: true
}
render() {
return (
<>
<input type="text" value={this.state.msg} onChange={
this.handleInput
}/>
<br />
{this.state.msg}
<hr />
<input type="checkbox" checked={this.state.isBeauty} onChange={
this.handleChecked
}/>
<br />
{
this.state.isBeauty
?
'美女你好'
:
'姑娘你好'
}
</>
)
};
handleInput =(e) => {
console.log(e.target.value);
this.setState({
msg: e.target.value
})
};
handleChecked = (e) => {
this.setState({
isBeauty: e.target.checked
})
}
}
父向子 props传递参数
子向父通信
原理:
父组件中定义 方法, 通过props 传递给子组件这个方法,子组件 调用 该方法 通过方法参数 可以 传递 子组件数据
class App extends Component {
state = {
msg: ''
}
render() {
return (
<div>
<h2>父组件</h2>
{this.state.msg}
<hr />
<Todo fn={this.fn}/>
{/*通过props将父组件方法 传递给子组件*/}
</div>
)
};
fn = (msg) => {
// 父组件方法
alert('我调用了'+ msg)
this.setState({
msg
})
}
}
class Todo extends Component {
state = {
msg: '这是子组件的数据'
}
render() {
return (
<div>
<button onClick={
() => {
this.props.fn(this.state.msg)
{/*子组件调用 父组件传递的方法*/}
}
}>子向父通信</button>
<h2>子组件</h2>
</div>
)
}
}
兄弟组件通信
可以利用PubSub 发布订阅的 js库进行兄弟组件通信
npm i PubSub
// 创建实例
const pubsub = new PubSub();
// 兄弟组件1 中 订阅一个消息
pubsub.subscribe('biubiu', (data) => {
})
// 兄弟组件2 中发布该消息
pubsub.publish('biubiu', 携带的数据)
import React, { Component, createRef } from 'react'
import Todo from './Todo'
export default class App extends Component {
constructor(){
super();
// 在实例上存储 容器
this.btnRef = createRef(null);
this.todoRef = createRef(null);
};
render() {
return (
<div>
{/* 挂载容器 将子组件实例dom存储到容器中*/}
<Todo ref={this.todoRef}/>
<button ref={this.btnRef}>按钮</button>
</div>
)
};
componentDidMount(){
// 在容器current属性中获取
console.log(this.btnRef.current);
console.log(this.todoRef.current);
};
}
五一作业:
回来收两个项目 pc和移动 录制 视频 等
提前学习react后面视频 学习到 react-router之前
组件即将卸载
使用场景:
注销一些全局挂载,比如 定时器、 全局事件等
类似于vue计算属性, 从组件已有的props和state中 派生出新的状态
react中 只要祖先组件更新, 后代默认一定会更新,不管导致 祖先组件更新数据 有没有在 后代组件中使用
问题?
后代组件 无意义的 re-render
class TodoItem extends Component {
shouldComponentUpdate(nextProps, nextState){
/*
函数 在每一次 子组件 更新render前触发, 拦截后代组件更新, return false 子组件 永远不更新
return true 子组件更新(只要祖先组件更新)
参数1
nextProps 如果更新,更新后 最新的props this.props更新的前组件props
nextState 更新最新的state this.state 更新的前state
*/
// console.log(nextProps.item, this.props.item);
return nextProps.item !== this.props.item
};
render() {
console.log('子组件render');
return (
<div>
<h2>{this.props.item}</h2>
<button onClick={
() => {
this.props.changeMsg(this.props.index)
}
}>改变值</button>
</div>
)
}
}
原理:
在每一次子组件更新前,比较新老props和新老state,如果有改变 则 shouldComponentUpdate return true让子组件更新,否则不更新
原理:
react 自动在 子组件 更新前 ,对于 所有 新老 props和state 进行浅层比较, 有改变 子组件更新没有改变子组件不更新
react函数式组件 问题?
比如 没有 内部状态 没有生命周期钩子
react在 16.8 推出了 react hook函数 解决函数式组件 问题
1 hook函数常见命名是 useXxx (use功能名)
2 hook函数 只能在 函数式组件 内部使用,其他函数 或者 外部无法使用的
解决函数式组件中 没有内部状态的问题
function App() {
/*
useState传入初始值 返回值 是数组
1 个 当前值
2 个 修改值得方法, 方法要求 必须传入一个新值 传入新值时 数据修改视图自动刷新
主要针对引用类型, 一定要克隆传入
*/
const [num, setNum] = useState(10);
const [arr, setArr] = useState([1,2,3,4]);
return (
<div>
<button onClick={
()=> {
setNum(num+1)
}
}>+</button>
{
num
}
<hr />
<button onClick = {
() => {
setArr([
...arr,
arr.length+1
])
}
}>增加li </button>
<ul>
{arr.map(el => (
<li key={el}>
{el}
</li>
))}
</ul>
</div>
)
}
注意:
useState返回set函数 修改值 一定要传入新值 主要针对引用类型,一定要 克隆后传入新值
useState set函数修改数据,是异步的,且不支持 异步回调
1 默认 会在初始化完成和更新完成都触发 相当于 componentDidMount和 componentDidUpdate
useEffect(() => {
// 相当于 componentDidMount和 componentDidUpdate
})
注意: 这种不要用,经常会造成死循环
2 useEffect 定义第二个参数 是数组 数组中定义 更新阶段触发的 依赖
更新阶段只有依赖中 任意一个发生改变时 才触发
功能:
类似 vue中watch(立即触发的watch)
解决 useState set函数修改数据 异步的问题
useEffect(() => {}, [a,b,c])
3 模拟 初始化完成的 生命周期钩子函数
作用: 类似 componentDidMount vue 的 onMounted
组件初始化 请求函数的调用
第三方插件的初始化
// 定义空的依赖即可
useEffect(() => {}, [])
4 模拟组件 卸载前的生命周期钩子函数
useEffect(() => {
return () => {
// 返回的函数 会在卸载前触发
}
}, [])
react 提供了 可以 实现 跨层级 组件 数据传递的方案
import { createContext } from "react";
// 创建context对象
const context = createContext();
/*
context对象下 有两个属性 都是react组件
1 Provider 数据提供者, 通过 value属性挂载公共的数据 只能有 Provider的后代组件 通过Consumer获取
2 Consumer
后台组件用于 获取Provider提供的value的
*/
const { Provider, Consumer } = context;
export {
Provider,
Consumer
}
const data = {
a: 10,
b: 20
}
root.render(
<Provider value={data}>
<App />
</Provider>
);
class A extends Component {
render() {
return (
<Consumer>
{
(data) => (
<div>
<h2>a组件</h2>
{data.a}
</div>
)
}
</Consumer>
)
}
}
面试题?
fn(5)(6) // 30
function fn(a){
return function(b){
return a*b
}
}
高阶组件 本质上就是 高阶函数(特殊高阶),用来抽象或者 修饰 普通组件,可以给普通组件 添加额外视图,或者额外props等
实现:
特殊高阶函数, 接收参数就是被修饰的组件, 返回了一个组件
注意:
withXxx
问题?
高阶组件 本质上劫持普通组件 到内部使用, 返回新的组件, 造成 普通组件的props丢失
需要在 高阶组件内部 再一次 传递 props给被修饰的组件
import React, { Fragment } from 'react';
const withTpl = (DecoratorComponent) => {
return (props) => {
console.log(props, '222');
return (
<Fragment>
<h1>这是头部</h1>
<DecoratorComponent {...props} msg="这是高阶组件送的props"/>
<h1>这是尾部</h1>
</Fragment>
)
}
}
使用
export default withTpl(被修饰的组件)
函数式组件中用于获取 dom 或者 class子组件实例
function Todo() {
// 创建容器
const btnRef = useRef();
const childRef = useRef();
const child2Ref = useRef();
useEffect(() => {
console.log(btnRef.current);
console.log(childRef.current);
}, [])
return (
<div>
<button ref={btnRef}>按钮</button>
<CommonHead ref={childRef}/>
</div>
)
}
问题?
函数式 组件 无法 通过ref 绑定到容器上
<CommonHead2 ref={child2Ref}/>
// 这是函数式子组件, 绑定容器可以通过 forwardRef 传递到子组件内部
forwardRef(function CommonHead2(props,child2Ref) {
return (
<div>
<h2 ref={child2Ref}>这是子组件2</h2>
</div>
)
})
将一个函数计算返回值 缓存起来,并返回, 指定依赖, 在 函数式组件多次 更新,依赖没有改变,使用缓存的值
功能类似于vue的计算属性
const sum = useMemo(() => {
return num1+num2
}, [num1, num2])
注意:
1 useMemo回调一定要有返回值,
2 useMemo手动指定依赖, 在函数式组件重新调用时,依赖改变 才重新计算
是useMemo语法糖
useMemo不同点在于:
1 useMemo 回调一定要有返回值, useCallback不需要
2 useMemo调用后返回的 是 回调 return 的值 useCallback直接返回 callback函数
3 都可以指定依赖, 依赖改变,useCallback 重新调用,重新返回新的callback,否则不调用使用上一次返回的callback函数
function Todo() {
const [num2, setNum2] = useState(20);
const [num3, setNum3] = useState(30);
// 当Todo刷新重新调用,只有num2改变 addNum2才会得到一个新的函数, 使用上一次缓存的函数
const addNum2 = useCallback(() => {
console.log(1);
setNum2(num2+1)
}, [num2])
return (
<div>
<button
onClick={addNum2}
>
num2+
</button>
{num2}
<br />
<hr />
<button
onClick={() => {
setNum3(num3 + 1);
}}
>
num3+
</button>
{num3}
</div>
);
}
函数式组件中 获取 context对象 Provider提供的value
const data = useContext(context); // 返回该 context Provider提供的value
特点:
万物皆组件
路由实现也是通过组件定义路由
提供了三个包
react-router 核心包 (包含了 react-router-dom和react-router-native)
react-router-dom 专门用于 b/s应用
react-router-native 专门用于 c/s 移动端app
npm i react-router-dom -S
包裹 App组件 路由才可以生效, 不同 根组件决定路由不同模式
HashRouter hash模式路由
BrowserRouter history模式路由
import { HashRouter } from 'react-router-dom'
<HashRouter>
<App/>
</HashRouter>
import { BrowserRouter } from 'react-router-dom'
<BrowserRouter>
<App/>
</BrowserRouter>
注意:
路由定义组件 即是路由定义 也是路由出口
import { Routes, Route } from 'react-router-dom'
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
<Route path="/news" element={<News/>}/>
</Routes>
Link
属性如下
to 控制路由跳转path 可以是 字符串(直接写路由地址) 对象 {pathname: ‘/xxx’}
replace boolean 跳转时 覆盖当前历史记录
state 对象 (传参)
注意:
Link只是单纯导航,对于匹配路由 没有做高亮样式的处理
NavLink
具有Link所有的属性,同时 增加了 匹配导航 高亮样式处理
<NavLink to="/news" className={({isActive}) => isActive?'active':'inactive'}>新闻页</NavLink>
<NavLink to="/news" style={
({isActive}) => isActive? {color:'red'}: {color: 'gray'}
}>新闻页</NavLink>
<NavLink to="/about" replace>
{
({isActive}) => (
<button className={isActive?'aaa':'bbb'}>关于我们</button>
)
}
</NavLink>
Navigate
注意:
Navigate 一定要有条件的渲染, 否则会造成路由死循环
to控制重定向的 地址
replace 默认为true
<Route path="/" element={<Navigate to="/home"/>}/>
利用表达式
{
!isLogin()
&&
<Navigate to="/login"/>
}
react-router 提供了特殊的path * 匹配任意路由地址 且优先级别最低
<Route path="*" element={<NotFound/>}/>
<Route path="/news" element={<News />}>
<Route path="/news/native" element={<NativeNews />} />
<Route path="abroad" element={<AbroadNews />} />
</Route>
function News() {
return (
<div>
<h3>这是新闻页</h3>
<Link to="/news/native">国内新闻</Link>
<Link to="abroad">国外新闻</Link>
<Outlet/>
</div>
)
}
const location = useLoaction()
结合useEffect路由监听
useEffect(() => {
console.log(location, 222);
}, [location])
注意:
默认情况下 App.jsx 根组件 在路由切换时 不会重新触发了
想要重新触发,监听 location即可
原因:
useLocation 每一次需要返回新的对象,必须重新执行 useLocation,让每一次路由切换时 App.jsx重新调用 再一次调用useLocation 返回新的location
const navigate = useNavigate();
// 普通跳转
navigate('/home')
// replace跳转
navigate('/home', {
replace: true
})
// 传递state 参数
navigate('/home', {
state: {
a: 10,
b: 20
}
})
动态路由传参
<Route path="/news/:id" element={<News />} />
navigate('/news/5')
const params = useParams();// 解析好的 动态参数
// params {id: 5}
state传参
<Link to="/news" state={{a: 10,b: 20}}>到新闻</Link>
navigate('/news', {state: {a: 10,b: 20}})
const location = useLoaction();
// location.state.参数名
注意:
隐式 传参
刷新不丢失
search传参
navigate('/news?a=10&b=20')
const [searchParams, setSearchParams] = useSearchParams();
/*
searchParams 对象 获取 search获取
searchParams.get('a') // 10 searchParams.get('b') // 20
setSearchParams 函数 动态设置search参数
setSearchParams('c=1000') 动态设置当前 url search参数的值
*/
{
!isLogin()
&&
<Navigate to="/login"/>
}
const navigate= useNavigate();
useEffect(() => {
if (!isLogin()) {
navigate('/login')
}
}, [])
<Route path="/about" element={
isLogin()? <About/> : <Navigate to="/login" />
} />
使用 React.lazy结合 Suspense 组件 实现
引入组件使用 lazy方法
import { lazy } from "react";
const About = lazy(() => import("../pages/About"))
使用 Suspense 组件 包裹 异步引入的组件
<Suspense fallback={
<div>
加载中...
</div>
}>
渲染异步的组件
</Suspense>
高阶组件 解决 react 函数式组件 性能问题?
react 祖先组件更新 后代组件一定会更新,不管 导致祖先组件更新数据 有无在后代组件中使用
class组件可以通过 shouldComponentUpdate和 PureComponent解决?
函数式组件怎么解决?
函数式组件 更新 会让函数重新调用
React.memo 这是高阶组件,功能 在祖先组件 刷新重新调用 后代组件 只有当他的props中任意一个改变 后代组件才会重新触发 刷新视图
import React, {memo} from 'react'
const Todo = (props) => {
return (
<div></div>
)
}
export default memo(Todo)
用的更多 基于 redux封装的库 比如 @reduxjs/toolkit dva mobx
js状态管理的库,
核心概念:
安装
npm i redux -S
import { legacy_createStore as createStore } from 'redux';
import { cloneDeep } from 'lodash'
const defaultState = {
num: 10
}
/*
reducer必须是纯函数
接收两个参数
state修改前 store中的state
参数2 组件 dispatch action
*/
const reducer = (state = defaultState, action ) => {
// state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新 必须深拷贝
const newState = cloneDeep(state);
return newState;
}
// 参数1 传入 仓库reducer
const store = createStore(reducer);
export default store
store.getState() // 获取仓库中的state
// 一般建议 将 获取的state 结合 useState存储, 当数据发生改变, 使用useState提供set函数 重新 store.getState() 重新赋值 让视图刷新
const [state, setState] = useState(store.getState());
// 触发一个行为,告诉仓库我们要做什么
/*
action是一个对象
必须有一个属性
type 做什么
*/
store.dispatch({
type: 'ADD_NUM',
data: 5
})
const reducer = (state = defaultState, action ) => {
// state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新 必须深拷贝
const newState = cloneDeep(state);
// 判断action type 修改state
switch (action.type) {
case 'ADD_NUM':
newState.num += action.data
break;
default:
break;
}
return newState;
}
// 订阅仓库 state 改变
store.subscribe(() => {
setState(store.getState())
})
redux 通过 异步 redux 插件来完成
以redux-thunk 举例
1 安装
npm i redux-thunk -S
2 store 使用插件
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer';
// 参数1 传入 仓库reducer
const store = createStore(reducer, applyMiddleware(reducer));
3 如何定义异步请求
改写 actionCreator
变成 函数 返回 函数
const fetch_items = (params = {}) => {
return dispatch => {
axios.get('xxx', {params}).then(res => {
if (res.data.code === 200) {
dispatch({
type: 'xxx',
data: res.data.data
})
}
})
}
}
4 组件中 dispatch 异步 actionCreator
store.dispatch(fetch_items(page: 1, pageSize: 10))
npm i react-redux -S
import { Provider } from 'react-redux';
import store from './store';
<Provider store={store}>
<App />
</Provider>
import { useSelector, useDispatch } from 'react-redux'
const num = useSelector(state => state.num);
const cates = useSelector(state => state.cates);
dispatch(fetch_cates())
dispatch(add_num(10))
scribe(() => {
setState(store.getState())
})
redux 通过 异步 redux 插件来完成
以redux-thunk 举例
1 安装
npm i redux-thunk -S
2 store 使用插件
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer';
// 参数1 传入 仓库reducer
const store = createStore(reducer, applyMiddleware(reducer));
3 如何定义异步请求
改写 actionCreator
变成 函数 返回 函数
const fetch_items = (params = {}) => {
return dispatch => {
axios.get('xxx', {params}).then(res => {
if (res.data.code === 200) {
dispatch({
type: 'xxx',
data: res.data.data
})
}
})
}
}
4 组件中 dispatch 异步 actionCreator
store.dispatch(fetch_items(page: 1, pageSize: 10))
npm i react-redux -S
import { Provider } from 'react-redux';
import store from './store';
<Provider store={store}>
<App />
</Provider>
import { useSelector, useDispatch } from 'react-redux'
const num = useSelector(state => state.num);
const cates = useSelector(state => state.cates);
dispatch(fetch_cates())
dispatch(add_num(10))