# react-shop
**Repository Path**: fxym888/react-shop
## Basic Information
- **Project Name**: react-shop
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-12-31
- **Last Updated**: 2022-01-12
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## react
用户构建用户界面的js库
## 特点
组件化
声明式开发
跨端
## 安装cra(create-react-app)
```js
npm i create-react-app -g
```
## 启动项目
```js
create-react-app 项目名 // ps不要出现大写字母
```
## 项目依赖三个包
+ react (核心语法包)
+ react-dom (虚拟dom 绑定到 index.html上)
+ react-scripts (webpack配置文件)
## jsx (明天讲原理)
xml in js 可以在js中写标签
```js
小明
18
{
name: '小明',
age: 18
}
```
明确概念:
虽然我们写的是标签,在运行之前 React 会自动 去 分析 你写jsx 编译成虚拟dom(对象)
为什么:
想象一下,在 react组件中 结构 如果直接 写 虚拟dom对象,会特别麻烦,且事件绑定都很麻烦
所以react就退出jsx语法
### jsx中语法
#### jsx中想写 js 语法 就加 {}
注意:
毕竟写在jsx中 (编译成 标签的内容),所以你能写的js语法 仅限于 表达式(最终有个值)
## 函数式组件
其实就是一个函数 返回 jsx
注意:
1 react所有组件首字母必须大写
2 react 所有的jsx 必须 包裹在一个闭合标签(必须有一个根元素)
```js
const App = (props) => {
return (
这是app组件
{ props.title }
)
}
// 相当于 调用 App这个函数 传递的自定义属性 会 作为 函数的 第一个参数props的属性
```
函数式组件问题:
没有内部的state
没有生命周期钩子函数
## class组件
定义class组件
```js
import React, { Component } from 'react'
class App extends Component {
render(){
console.log(this);
return (
我是app组件
{ this.props.title }
{ this.props.subTitle }
)
}
}
export default App
```
使用
```js
/*
1 以标签使用组件 立即 new App 并调用实例的render方法
2 传递的自定义属性 挂载到 实例 的props属性上
*/
```
优点:
可以定义内部的状态
有 生命周期
## jsx原理
为什么要有jsx:
react组件需要有虚拟dom 定义组件结构,如果直接用js对象写虚拟dom,那么就太麻烦,所以react退出了jsx(允许我们在js中写 标签结构,自动调用React.createElement编译成虚拟dom)
```js
// 模拟虚拟dom
{
tag: 'div',
attrs:{
id: 'box',
className: 'wrap'
},
children: [
{
tag: 'p',
attrs: {
className: 'op'
},
children: [
'这是p'
]
},
{
tag: 'span',
attrs: null,
children: [
'这是span'
]
},
'这是文本'
]
}
```
问题来了 如果 写的标签 没有经过编译 在js中一定会报错,
在组件运行之前,React会根据标签 嵌套结构,分析 虚拟dom结构,并自动调用React.createElement来编译成虚拟dom
```js
// 这是jsx
// 运行之前
React.createElement('div',
{
id: 'box',
className: 'wrap'
},
children: [
React.createElement('p', {className: 'op'}, children: ['这是p']),
React.createElement('span', {}, children: ['这是span']),
'这是文本内容'
]
)
```
## 组件中的样式
### 行内样式
style属性时 对象
```js
```
### 引入外部样式表
cra 默认配置 css预处理器是 sass
注意:jsx中标签的 class属性需要变成className
```js
import 'xxx/xx.css'
import 'xxx/xx.scss'
```
注意:
node-sass经常会没有安装 包 找不到 sass错误,手动安装 node-sass(注意版本问题(5或者4版本))
### css in js styled-components
react 推崇: 万物皆组件
+ 基础
```js
import { styled } from 'styled-components'
/*
创建一个Box组件 且 会渲染成div标签,且具有 以下的样式
*/
const Box = styled.div`
width: 200px;
height: 200px;
background: pink;
`
// 使用时
```
+ 选择器嵌套
当我们组件中间需要嵌套其他后台元素时,可以通过选择器嵌套定义其他后代元素的样式
```js
const Box2 = styled.div`
width: 200px;
height: 200px;
background: skyblue;
p{
width: 100px;
height: 100px;
background: blue;
&:hover{
background: red;
}
}
`
```
+ 样式组件 传递props
```js
const Box3 = styled.div`
width: 200px;
height: 200px;
background: ${ props => props.bgc?props.bgc: 'pink'};
`
```
+ 继承
```js
// 定义一个普通按钮组件
const Qfbtn = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// 继承另一个样式组件的样式
const QfBtn2 = styled(Qfbtn)`
background: ${props => props.bgc?props.bgc:'yellow'}
`
```
+ 动画
```js
import styled, { keyframes } from 'styled-components'
// 定义动画
const rotate = keyframes`
0% {
transform: rotate(45deg)
}
100% {
transform: rotate(-45deg)
}
`
// 动画 定义一个动画元素
const Move = styled.div`
width: 10px;
height: 100px;
background: red;
margin: 50px auto;
transform-origin: center bottom;
animation: ${rotate} 100ms linear alternate infinite;
`
```
## 组件数据挂载--外部数据 props
利用 prop-types 做 props类型验证
### 安装 prop-types
```js
npm i prop-types -S
```
### 新增组件的 静态属性
```js
import PropTypes from 'prop-types'
class MyComponent extends Component{
/*
内部
static propTypes = {
}
*/
}
MyComponent.propTypes = {
// 必须是数组
a: PropTypes.array,
// 必须是布尔值
b: PropTypes.bool,
// 必须是函数
c: PropTypes.func,
// 必须是number
d: PropTypes.number,
// 必须是对象
e: PropTypes.object,
// 必须是字符串
f: PropTypes.string,
// 必须是 symbol
g: PropTypes.symbol,
// react组件 (实例) (ie. ).
h: PropTypes.element,
// react组件 (ie. MyComponent).
i: PropTypes.elementType,
// 必须是 Message实例对象
j: PropTypes.instanceOf(Message),
// 必须是给定值中的其中一个
k: PropTypes.oneOf(['News', 'Photos']),
// 值得类型 可以是给定类型中的其中一个
l: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 必须是数组 值 必须是给定类型
m: PropTypes.arrayOf(PropTypes.number),
// 对象 且属性的值必须是给定的类型
n: PropTypes.objectOf(PropTypes.number),
// 对象 必须要有给定 属性 且值 符合给定类型(不限制 对象的其他属性)
o: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 对象必须 只能有给定属性 (不能有其他属性),属性的值符合给定类型
optionalObjectWithStrictShape: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 链式 同时判断类型和必须传递
requiredFunc: PropTypes.func.isRequired,
// 只要求必须传递 但不要求类型
requiredAny: PropTypes.any.isRequired,
}
```
### props默认值
新增静态属性 defaultProps 属性定义prop默认值
```js
class MyComponent extends Component{
}
MyComponent.defaultProps = {
a: '默认值'
}
```
### props.children 类似于vue中的插槽
在子组件获取 组件标签 嵌套的内容
```js
嵌套内容
// 子组件内部
this.props.children
```
## react class组件中 可以定义 state属性 管理组件内部状态
### 实例上定义state来管理组件内部状态
```js
class Todo extends Component{
// 直接定义
/*
state = {
msg: '你好react',
isTrue: true
};
*/
// 在constructor中定义
constructor(){
super();
this.state = {
msg: '你好react',
isTrue: false
}
}
render(){
return (
{this.state.msg}
{
this.state.isTrue
?
'真的'
:
'假的'
}
)
}
}
```
### 修改state
react不是mvvm框架 直接修改state视图不会刷新
需要调用setState修改状态
+ 第一个参数传对象 直接在对象中定义需要修改状态
```js
this.setState({
isTrue: !this.state.isTrue
})
```
+ 第一个参数是 函数 return对象在return对象中修改
好处:两个参数,修改前的state以及组件的props
```js
this.setState((state, props) => {
return {
isTrue: !state.isTrue
}
})
```
### setState修改状态异步处理
setState修改状态 有时候是异步的 有时候是同步的
异步情况:
react能够捕获到的操作 就是异步的 (react合成事件中修改状态、react生命周期钩子函数中修改状态)
同步的:
react捕获不到操作(原生的事件绑定中改变状态、定时器中)
在异步的情况下,如何获取修改后最新的数据和dom
setState第二个参数,回调函数,在状态修改完成视图刷新完成后触发
```js
this.setState({
isTrue: !this.state.isTrue
}, () => {
// 在这里可以获取最新状态
})
```
## 单向数据流
父子组件 数据流向是单向的,数据由props 从父组件 流向子组件 子组件不能直接修改
## 组件分类
逻辑组件(定义state管理状态) 和 UI组件(通过props接收)
有状态组件和无状态组件
受控组件(数据全部都是props接收)
非受控组件(没有props)
半受控组件 (有props也有state)
总结:
逻辑 组件 定义state管理状态, UI 尽量使用props 来管理状态(遵循单向数据流)
## react事件
### react中如何绑定事件
react 结合原生事件,合成 react事件
on事件名首字母大写
比如:
onClick
onChange
```js
{
render(){
return (
)
}
}
```
### 绑定事件函数的几种方式
+ 行内新增箭头函数 充当事件函数
不推荐的:因为 将逻辑代码嵌入 模板文件
```js
{
render(){
return (
)
}
}
```
+ 原型上定义一个方法 在行内通过bind 改变this指向
?
原型上定义方法this为什么是undefined:react干的(不指向实例 不是实例调用
```js
{
render(){
return (
)
};
handleClick(){
this.setState({
isTrue: !this.state.isTrue
})
}
}
```
不推荐:
因为render会多次触发(初始化和更新时都会触发)
调用bind多次调用 返回多个函数
+ 方法定义在原型上,在 constructor中 在实例上 定义一个同名方法,值是 原型上方法 bind改变this指向返回的方法
推荐使用
```js
{
constructor(){
super();
this.handleClick = this.handleClick.bind(this);
};
render(){
return (
)
};
handleClick(){
this.setState({
isTrue: !this.state.isTrue
})
}
}
```
+ 直接定义在实例 key=箭头函数
推荐使用
```js
{
render(){
return (
)
};
handleClick = ()=>{
this.setState({
isTrue: !this.state.isTrue
})
}
}
```
## 事件对象
事件 函数的 第一个参数是事件对象
做什么事情
```js
// 取消冒泡
e.stopPropagation()
// 阻止默认事件
e.preventDefault()
// 获取事件源
e.target
```
## 事件传参
在 行内新增箭头函数 作为事件函数,箭头函数中调用实例上的方法
```js
import React, { Component } from 'react'
export default class Todo extends Component {
render() {
return (
)
};
handleClick = (i, e) => {
console.log(i);
console.log(e);
}
}
```
## react数据渲染
+ 条件渲染
使用三目或者短路
```js
{
this.state.isShow
&&
}
// 或者
{
this.state.isShow
?
:
}
```
### 列表渲染 循环
利用数组 map方法进行渲染
map回调函数中 return 循环列表元素,每个列表元素需要加 独一无二的key属性(给fiber算法 提交dom渲染效率)
```js
{
state = {
arr: ['a', 'b', 'c', 'd']
};
render(){
return (
{
this.state.arr.map((item, index) => {
return (
-
{item}
)
})
}
)
}
}
```
### 渲染富文本
```js
```
### react提供一个容器组件(实际是不渲染)用来充当jsx根组件
```js
import React, { Component, Fragment } from 'react'
export default class Todo extends Component {
render() {
return (
ddwdw
)
}
}
```
## react 祖先后代组件更新机制
react 如果 一个祖先组件更新了,那么他的所有后台都会更新
不管 导致更新的数据都没有在后代组件中使用(vue不会,后代组件只有 改变的数据在后代组件中用了才会更新)
导致 一个性能的浪费:
解决:
react在提供了一个钩子函数 shouldComponentUpdate 手动决定这个组件是否更新
(可以手动判断,后代组件中的数据有没有发生改变,改变才更新否则不更新)
原理:
钩子函数有1个参数
如果组件更新 更新后最新的props
this.props是更新前的props
钩子函数return一个布尔值
true 则 祖先组件更新 后代永远更新
false 祖先组件更新 后代永远不更新(后代组件永远都不更新了,不管是否是组件导致的更新)
原理:
判断nextProps(最新props)和改变前的props this.props 如果发生改变则return true否则return false 注意:不能直接比较props(对象),props中找
会在后代组件发生改变的那个prop
{
shouldComponentUpdate(nextProps){
// nextProps 组件如果更新,最新的props this.props组件更新前的props
// return一个布尔值 true则 永远更新,false永远不更新 默认true
/*
判断 后代组件中 有可能发生变化的props ,如果发生改变了则return true
否则return false
*/
return nextProps.finished !== this.props.finished
};
}
PureComponent 解决后代组件 更新性能问题
注意:
进来不要使用shouldComponentUpdata 解决性能问题,使用内置的 PureComponent
原理:
对于 后代组件的 state和props进行浅层比较(某个prop或者state是对象嵌套对象不能判断)
```js
import React, { PureComponent } from 'react'
class XXX extends PureComponent{
}
```
## react组件通信方案
props (父子组件通信)
缺点:当父子组件层级 嵌套 较多时,代码可读基本是没有的
context做组件通信
+ 引入createContext 创建context对象
```js
import { createContext } from 'context'
const context = createContext();
/*
context对象下 有两个属性
Provider 数据的提供者,也是一个组件,通过这个组件的value属性来提供数据
提供的数据只能由 他的后代组件来通过Consumer获取
Consumer 组件 获取数据
*/
```
+ 导出Provider和Consumer属性
- 在入口函数中引入 包裹App组件,让Provider 变成祖先组件,并通过value 提供数据
```js
ReactDom.render(
,
document.querySelector('#root')
)
```
- 在任意一个组件中 引入Consumer获取Provider提供的数据
Consumer作为根标签,内容是一个函数return jsx函数参数就是Provider提供的数据
```js
class Componenta extends Component {
render() {
return (
{
({a, b}) => {
return (
我是a组件
{ a }
{ b }
)
}
}
)
}
}
```
## 高阶组件 HOC (hign order component)
面试题
```js
fn(2)(3)(4) // 24
// 函数柯里化 外层函数称为高阶组件
function fn(a){
return function(b){
return function(c){
return a*b*c;
}
}
}
```
高阶组件 其实 是一个高阶函数,只不过,return 不是函数而是 一个组件,且接收一个参数,参数也是一个组件(参数 是被修饰的组件)
高阶组件:
用来对于普通的组件做修饰,可以给普通组件增加一个 公共的模板结构(html),一些额外的props
### 定义高阶组件
```js
import React, { Component, Fragment } from 'react'
// 高阶组件是一个函数
const withHeader = (DecoratorComponent) => {
return class WithHeaderComponent extends Component{
render(){
return (
)
}
}
}
export default withHeader
```
### 使用
```js
withHeader(被修饰的普通组件)
```
### 问题:
一个普通组件被高阶组件修饰后,被劫持,在高阶组件 返回的组件中使用,真正导出的不再是普通组件,而是高阶组件return的组件,会导致 使用组件时传递的props丢失(去高阶组件内部 返回组件中了),再一次传给 普通组件
```js
const withHeader = (DecoratorComponent) => {
return class WithHeaderComponent extends Component{
render(){
return (
)
}
}
}
```
## react 组件props 可以通过展开一个对象 来批量传props
```js
const obj = {
a: 10,
b: 20,
c: 20
}
// 相当于 给a组件传递了 三个prop 分别是 a b c
```
## ref 转发 dom和组件
```js
import React, { Component, Fragment, createRef } from 'react'
import Todo from './Todo'
export default class App extends Component {
constructor(){
super();
// 实例上定义容器 存档组件实例和dom对象
this.todoRef = createRef();
this.btnRef = createRef();
};
render() {
return (
{/* 将dom对象和组件实例 挂载到容器上 */}
)
};
componentDidMount(){
console.log(this.todoRef.current);
console.log(this.btnRef.current);
}
}
```
## redux js状态管理的库
采用集中式状态管理
### 三大原则
+ 单一数据源 state只有一个
+ state是只读的 (不能在组件中直接改变state)
+ reducer是一个纯函数
函数 return的值 取决于 接收参数,且中间没有副作用
### redux初始化
+ 下载redux
```js
npm i redux -S
```
+ 初始化仓库
```js
// 引入 createStore
import { createStore } from 'redux'
// 准备好reducer
// 仓库初始值 在reducer 生成
const defaultState = {
num: 10
}
// 定义reducer 是一个纯函数
const reducer = (state = defaultState, action) => {
// 深克隆state reducer要求返回一个新的state
const newState = JSON.parse(JSON.stringify(state));
return newState
}
// 定义仓库 传入 reducer
const store = creatStore(reducer)
```
问题?
为什么 初始值定义在reducer
仓库初始化时 立即调用reducer 没有传state,state等于定义的默认值,返回一个新的state,仓库立即保存(此时仓库中就有了状态)
+ 组件中获取store state
将 store.getState() 返回仓库中的状态 挂载组件的state上
```js
{
constructor(){
super();
this.state = {
...store.getState()
}
}
}
```
### dispatch action 触发 store中状态的更新
组件中dispatch action
action是什么
是一个对象
有一个固定的属性
type 代表 action 类型 指令是干啥的
其他属性随便,用来参与改变state的
```js
{
type: 'addNum',
value: 10
}
```
```js
store.dispatch({
type: 'addNum',
value: 10
})
```
reducer会立即触发,在reducer中判断 action的type做 操作state的修改
```js
const reducer = (state = defaultState, action) => {
// 深克隆
const newState = JSON.parse(JSON.stringify(state));
// 根据组件 传入的action指令 做state修改
switch (action.type) {
case 'addNum':
newState.num += action.value
break;
default:
break;
}
return newState
}
```
state修改完 组件中添加 观察者 观察仓库中的 状态变化 并调用setState触发组件的更新
```js
store.subscribe(() => {
this.setState({
...store.getState()
})
})
```
### actionCreators
问题:
如果在组件中直接 写action,如果多个组件 需要提交同一action,不存在复用性和可维护性,不同组件提交同一action可能传的参数不一致
解决方案:
单独提取actionCreators(创建action的函数)
外部定义 函数 返回 action 单独管理action,哪个组件中要提交action,直接引入actionCreator,调用传入不同参数即可
### 单独提取 action的type使用常量保存
### 安装 chrome-redux-devtool在谷歌浏览器调试 redux
```js
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
```
### redux处理异步请求
场景:
store中有一个状态,初始值 需要请求 一个接口才能拿到数据
将初始值 获取 函数定义在redux中
#### redux处理异步插件
redux-thunk
redux-promise
redux-saga (generator)
### redux如何使用插件
```js
import { createStore, applyMiddleware } from 'redux'
const store = createStore(
reducer,
applyMiddleware([...插件])
)
```
### redux-thunk的使用
```js
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
// 创建仓库时 要传入 他的reducer
const store = createStore(
reducer,
applyMiddleware(thunk)
);
```
actionCreator原来的写法是一个函数return action(对象),
redux-thunk使用后 actionCreator可以是 一个函数 return 一个函数,内层函数中可以发送异步请求,
这个actionCreator用法和普通还是一样的 store.dispatch(actionCreator)
定义异步actionCreator
```js
import {fetchItems} from '_api'
const fetch_items = (page=1, pageSize=10) => {
return (dispatch) => {
fetchItems({
page,
pageSize
}).then(res => {
if(res.data.code === 0) {
dispatch({
type: 'init_items',
items: res.data.data
})
}
})
}
}
const reducer = (state = defaultState, action) => {
....
switch(action.type){
case 'init_items':
newState.items = action.items
}
}
// 触发异步action
store.dispatch(fetch_items(2, 10))
```
### 拆分多个reducer
当公共状态较多时,一个reducer管理,代码 过于臃肿,难以维护,可以将不同模块的数据 定义多个reducer单独管理
## lodash
## react-redux
连接redux仓库和react组件(利用context和高阶组件将 仓库中的state挂载到组件的props上,将提交action的方法也挂载到 组件的props上)
### 安装
```js
npm i react-redux -S
```
+ 入口函数中引入 store和Provider 挂载store
```js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
,
document.querySelector('#root')
)
```
+ 在组件内部 通过connnect来连接
```js
import { connect } from 'react-redux'
class MyComponent extends Component{
}
/*
参数
state 就是 store的state
要求return一个对象
对象的属性就会挂载到组件的props
*/
const mapStateToProps = (state) => ({
itemNum: state.item.num,
orderNum: state.order.num
})
// 将提交action的方法挂载到组件的props
/*
参数 dispatch 提交action方法
return 对象
对象的方法会挂载到组件的props
*/
const mapDispatchToProps = (dispatch) => ({
itemAddNum: (value) => {
dispatch(item_add_num(value))
},
orderAddNum: (value) => {
dispatch(order_add_num(value))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Todo)
```
## hook
解决函数式组件 一些缺陷
没有state 没有生命周期
注意:
所有的hook函数一定要在 函数式组件内部使用
### useState
解决函数式组件没有内部状态问题
useState调用传入初始值 返回 一个数组 第一个 是 初始值 第二个是修改这个值的方法,通过这个方法修改值 视图会自动刷新
```js
import React, { useState } from 'react'
const Todo = () => {
let [num, setNum] = useState(10);
return (
{ num }
)
}
```
useState返回的 修改数据的方法,要求传入一个新值,视图才会刷新,否则不刷新
针对引用类型,如果还是修改 原数据,传入改完的原数据,传递的是地址,判断数据是没有改变,视图不会刷新
注意:引用修改时 一定要传入新值(克隆)
关于引用类型
```js
import React, {useState} from 'react'
export default function A() {
let [arr, addArr] = useState([1,2,3,4]);
return (
{
arr.map(el => {
return (
-
{el}
)
})
}
)
}
```
### useEffect
解决函数式组件 没有生命周期钩子的问题
+ useEffect省略第二个参数
```js
import React, {useEffect, useState} from 'react'
import { cloneDeep } from 'lodash'
const Todo = () => {
let [items, setItems] = useState([]);
const fetchItems = () => {
fetchItems().then(res=> {
if(res.data.code === 0) {
setItems(cloneDeep(res.data.data))
}
})
}
useEffect(() => {
/*
初始化:相当于 class组件中的componentDidMount时触发(dom渲染完成)
更新阶段:相当于componentDidUpdate
问题是:
如果在 useEffect中 调用 初始请求函数,当值返回后,会
调用 useState 修改数据函数,导致 组件刷新,刷新时useEffect再次调用,导致刷新 会产生死循环
*/
fetchItems()
})
return (
)
}
```
+ useEffect 加 第二个参数
useEffect第二个参数 是一个数组 数组中定义 更新的依赖(只有依赖的变量发生改变后才会更新)
什么时候用:
请求函数初始调用,定义一个useEffect,给一个依赖,依赖的值永远不会发生改变(充当componentDidMount)
```js
import React, {useEffect, useState} from 'react'
import { cloneDeep } from 'lodash'
const Todo = () => {
let [items, setItems] = useState([]);
const notChange = '值不变';
const fetchItems = () => {
fetchItems().then(res=> {
if(res.data.code === 0) {
setItems(cloneDeep(res.data.data))
}
})
}
useEffect(() => {
// 只会在初始阶段触发 或者notChange发生改变触发
fetchItems()
}, [notChange])
return (
)
}
```
### useContext 函数式组件中 用于获取 context对象 Provider提供的value
```js
import { useContext } from 'react'
useContext(context)
// 返回就是这个context Provider提供的value
```
## useRef 在函数式组件中转发dom和子组件实例
```js
import React, { useRef, useEffect } from 'react'
import B from './B'
export default function A() {
const notChange = 'xxx';
// 定义容器 保存 获取的dom对象和组件实例
const h1Ref = useRef();
const bRef = useRef();
useEffect(() => {
console.log(h1Ref.current);
console.log(bRef.current);
}, [notChange]);
return (
{/* 添加ref属性 转发 */}
a组件
)
}
```
## useMemo 记忆函数
给定一个依赖 当依赖发生改变时 回调触发 否则不触发
用于函数式组件 父子组件性能优化
函数式组件中 父组件更新 子组件也是函数组件,子组件一定也会更新
```js
function A() {
let [num, setNum] = useState(0);
let [arr, addArr ] = useState([1,2,3])
return (
a组件
)
}
/*
父组件A中 有两个状态都会改变,改变都会导致A组件的更新
B也会重新触发
问题:
其中 arr发生改变 arr并没有在子组件B中使用,所有 B组件没有必要重新调用
阻止不了
解决:将B组件中的逻辑代码 扔到useMemo记忆函数中,记忆 依赖 num这个props
num发生改变 useMemo回调才触发
*/
// B组件
import React, { useMemo } from 'react'
export default function B(props) {
const fn = () => {
console.log(11);
}
useMemo(() => {
fn();
}, [props.num])
return (
b组件
{props.num}
)
}
```
## react-router
https://reactrouter.com/
### 安装
万物皆组件
路由也是由组件定义(不同于vue options)
```js
npm i react-router-dom -S
```
### 路由根组件
只有 使用这个组件 作为 应用根组件 才能使用路由
+ HashRouter
hash模式路由
+ BrowserRouter
history模式路由
### 定义路由组件
Route
属性
```
path 定义 路由地址
component 定义路由组件
注意:
1 路由path匹配规则的是 地址栏后面的path 开头 是以 当前Route path开头就可匹配 (/这个path永远会被匹配)
2 如果有多个 Route 那么默认是 贪婪模式 匹配成功一个Route不会停 会继续向下匹配下一个Route
```
### Switch
用于包裹 Route
好处:用Switch包裹 多个Route,那么就只能匹配成功一个Route
### Link
控制路由跳转的
```
to属性控制 跳转的path(字符串 或者对象)
replace 布尔值 是否覆盖当前历史记录
```
### NavLink
具有Link所有的属性
具有默认 高亮类 active
```
activeClassName 自定义高亮的类
activeStyle 自定义高亮内联样式
exact 导航组件高亮 状态(activeClassName activeStyle)必须地址栏抵制 和 NavLink 的 to 一模一样
```
### 重定向
Redirect
```
to 定义重定向的地址
from 当前路径是多少时 重定向到to的path
exact 修饰from 精准匹配
```
### 二级路由
只需要 在一级路由对应的组件中 通过Route定义二级路由即可
注意:
1 二级路由path 必须携带 一级路由path作为路径前缀
2 一级路由一定不能加精准匹配 exact
```js
// 一级路由
// 二级路由 News组件中定义二级路由
```
## 编程式导航
所有路由组件(Route组件 component属性对应的组件就是路由组件)
路由 给 路由组件的props中灌入 三个对象
history
location
match
history下 有4个方法
```js
listen() // 监听路由变化 需求 需要监听所有路由变化 应该在App组件中调用 App不是路由组件
go(n) // 操作历史记录
push() // 跳转路由 添加新的历史记录
replace() // 跳转路由 覆盖当前 路由历史记录
```
## 非路由组件的props中获取 三大对象
react-router 提供了一个 高阶组件 withRouter 修饰一下普通组件即可
```js
withRouter(普通组件)
```
## class组件 在constructor中获取props
class组件在constructor触发时,其实 还没有 给实例灌入props,所有拿不到
接收参数传递给 super即可在constructor拿到props
```js
class App extends Component {
constructor(props){
super(props);
console.log(this.props);
}
}
```
## 路由跳转传参
ps:不管是 声明式还是编程式 传参取决于 跳转 的 传的参数 (不管是to属性还是编程式导航)
### 动态路由传参
+ 定义动态路由
```js
```
+ 跳转传参 保持 格式一致性
```
'/news/2'
```
+ 目标路由组件获取
```js
this.props.match.params.newsId
```
### state 传参
+ 跳转参数是 对象
```js
{
pathname: '/news',
state: {
a: 10,
b: 20
}
}
```
+ 目标组件获取
```js
this.props.location.state.xxx
```
注意
不在地址栏体现
history模式下刷新不丢失 hash模式下刷新丢失
### query 传参
+ 传参
```js
{
pathname: '/news',
query: {
a: 10,
b: 20
}
}
```
+ 获取
```js
this.props.location.xxx
```
注意:
不在地址栏体现
刷新后丢失数据
### search传参
+ 传参
```js
'/news?a=10&b=20'
```
+ 获取
```js
this.props.location.search
/*
注意得到结果是 '?a=10&b=20'需要手动解析
*/
```