# 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

这是p

这是span 这是文本
// 模拟虚拟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

这是p

这是span 这是文本
// 运行之前 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'需要手动解析 */ ```