# reactStudy
**Repository Path**: ct970823/reactStudy
## Basic Information
- **Project Name**: reactStudy
- **Description**: react学习
- **Primary Language**: JavaScript
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-06-04
- **Last Updated**: 2021-09-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: React, JavaScript, Ant-Design
## README
# react学习
### 创建虚拟dom
1. 普通方式
```html
```
2. JSX方式(注意:type是text/babel 因为需要babel来解析jsx)
```html
```
### 自定义组件
1. 工厂函数组件(简单组件)
```jsx
function MyComponent() {
return
今天天气真{flag ? '炎热' : '凉爽'}
{/*
*/}
{
alert(this.myRef.current.value)
alert(this.myRef2.current.value)
}
render () {
return (
)
}
}
```
### state状态
1. 放在constructor中
```jsx
class xxx extends React.Component {
constructor(props) {
super(props);
//初始化状态
this.state = {
pwd: ''
}
}
}
```
2. 直接写在class中
```jsx
class xxx extends React.Component {
state = {
pwd: ''
}
}
```
### 受控组件和非受控组件 (推荐使用受控组件,因为可以减少ref的使用)
1. 受控组件(每次发生改变时,都会去调用)
```jsx
class xxx extends React.Component {
state = {
username:'',
password:''
}
handleSubmit = () => {
console.log(`你输入的用户名是${username},密码是${password}`);
}
render() {
return(
用户名:this.setState({username:e.target.value})} name="username"/>
密码:this.setState({password:e.target.value}} name="password"/>
)
}
}
```
3. 非受控组件(随调随取,调用时再去取值)
```jsx
class xxx extends React.Component {
handleSubmit = () => {
console.log(`你输入的用户名是${this.username.value},密码是${this.password.value}`);
}
render() {
return(
用户名:this.username = c} name="username"/>
密码:this.password = c} name="password"/>
)
}
}
```
### 生命周期 ([生命周期图谱](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/))
1. 旧版本
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render() =====> 必须使用的一个
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
4. 注意:在16.3版本时为不安全的生命周期引入别名(这里的 “unsafe” 不是指安全性,而是表示使用这些生命周期的代码在 React 的未来版本中更有可能出现 bug,尤其是在启用异步渲染之后。) UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate
```jsx
//创建组件
class Count extends React.Component{
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state
return(
当前求和为:{count}
)
}
}
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
)
}
}
//子组件B
class B extends React.Component{
//组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
我是B组件,接收到的车是:{this.props.carName}
)
}
}
```
1. 新版本
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. getDerivedStateFromProps (新增的) 必须使用静态方法(即在前面加上static),必须有返回值返回值可以是state或null
3. render()
4. componentDidMount() =====> 常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps(新增的) 必须使用静态方法(即在前面加上static),必须有返回值返回值可以是state或null
2. shouldComponentUpdate()
3. render()
4. getSnapshotBeforeUpdate(新增的) 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
5. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
```jsx
class Count extends React.Component {
//构造器
constructor(props) {
console.log('Count---constructor');
super(props);
//初始化状态
this.state = { count: 0 };
}
//加1按钮的回调
add = () => {
//获取原状态
const { count } = this.state;
//更新状态
this.setState({ count: count + 1 });
};
//卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
};
//强制更新按钮的回调
force = () => {
this.forceUpdate();
};
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
return null;
}
//在更新之前获取快照
getSnapshotBeforeUpdate() {
console.log('getSnapshotBeforeUpdate');
return 'atguigu';
}
//组件挂载完毕的钩子
componentDidMount() {
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount() {
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('Count---shouldComponentUpdate');
return true;
}
//组件更新完毕的钩子
componentDidUpdate(preProps, preState, snapshotValue) {
console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
}
render() {
console.log('Count---render');
const { count } = this.state;
return (
当前求和为:{ count }
);
}
}
```
### 脚手架
```shell
npx create-react-app my-app
```
### 类型检查
使用PropTypes来进行类型检查
```javascript
//1. npm下载prop-types包 npm install --save prop-types
//2. 引入prop-types
import PropTypes from 'prop-types';
//3. 使用
class xxx extends React.Component {
static propTypes = {
comments: PropTypes.array.isRequired,
delComment: PropTypes.func.isRequired
}
}
```
### 组件通讯
1. 父传子
- 父组件
```javascript
class xxx extends React.Component {
state = {
comments: [
{ username: 'jack', content: 'React真好玩' },
{ username: 'rose', content: 'React真有意思' },
]
}
//删除指定评论(父组件执行)
delComment = (index) => {
const { comments } = this.state
comments.splice(index, 1)
this.setState({ comments })
}
render() {
//解构赋值取到comments
const { comments } = this.state
const display = comments.length === 0 ? 'block' : 'none'
return (
评论回复:
暂无评论,点击左侧添加评论!!!
{
//自定义属性值将comment,index和delComment方法调用传给子组件
comments.map(
(comment, index) =>
)
}
)
}
}
```
- 子组件
```jsx
class xxx extends React.Component {
//删除当前评论
handleClick = () => {
//解构赋值取到comment,index,delComment
const {comment,index,delComment} = this.props
//提示
if(window.confirm(`确认删除${comment.username}的评论吗?`)){
//删除(调用父组件的方法)
delComment(index)
}
}
render() {
//解构赋值取到comment
const {comment} = this.props
return (
{comment.username}说:
{comment.content}
)
}
}
```
2. PubSub(订阅)
```shell
//npm下载pubsub-js包
npm install --save pubsub-js
//引入
import PubSub from 'pubsub-js'
```
- 兄弟组件1
```javascript
//通过publish发布消息
PubSub.publish('search',searchName)
```
- 兄弟组件2
```javascript
//通过subscribe订阅消息
PubSub.subscribe('search', (msg,searchName) => {
//发送请求xxxx
})
```
3. redux
1. npm 下载redux包和开发插件 redux-devtools-extension npm install --save redux redux-devtools-extension
2. 新建四个文件夹

- store.js
```javascript
/*
* redux 核心管理模块
* */
import {applyMiddleware, createStore} from "redux";
//异步
import thunk from "redux-thunk";
//开发插件
import {composeWithDevTools} from "redux-devtools-extension";
import reducers from "./reducers";
export default createStore(reducers,composeWithDevTools(applyMiddleware(thunk)))
```
- reducers.js
```javascript
/*
* 包含n个reducer函数,根据老的返回新的
* */
//通过combineReducers来拆分reducer
import {combineReducers} from 'redux'
import {
AUTH_SUCCESS,
ERROR_MSG,
RESET_USER,
RECEIVE_USER,
RECEIVE_USER_LIST,
RECEIVE_MSG,
RECEIVE_MSG_LIST,
MSG_READ
} from './action-type'
import {getRedirectTo} from "../utils";
const initUser = {
username:'',//用户名
type:'',//用户类型dashen/laoban
msg:'',//错误提示信息
redirectTo:''//需要自动重定向的路由路径
}
//产生user状态的reducer
function user(state=initUser,action) {
switch (action.type) {
case AUTH_SUCCESS://data是user action.data
const {type,header} = action.data
return {...action.data,redirectTo: getRedirectTo(type,header)}
case ERROR_MSG://data是msg
return {...state,msg:action.data}
case RECEIVE_USER://data是msg
return action.data
case RESET_USER://data是msg
return {...initUser,msg:action.data}
default:
return state
}
}
export default combineReducers({
user,
})
```
- action-type.js
```javascript
/*
* 包含n个action名称
* */
export const AUTH_SUCCESS = 'auth_success'//注册/登录成功
```
- action.js
```javascript
/*
* 包含n个action
* */
import {
AUTH_SUCCESS,
ERROR_MSG,
RECEIVE_USER,
RESET_USER,
RECEIVE_USER_LIST,
RECEIVE_MSG_LIST,
RECEIVE_MSG,
MSG_READ
} from './action-type'
//引入api
import {
reqRegister,
reqLogin,
reqUpdateUser,
reqUser,
reqUserList,
reqChatMsgList,
reqReadMsg
} from '../api'
//授权成功的同步action
const authSuccess = (user) => ({type: AUTH_SUCCESS, data: user})
//错误提示信息的同步action
const errorMsg = (msg) => ({type: ERROR_MSG, data: msg})
//注册异步action
export const register = (user) => {
const {username, password, password2, type} = user
//表单验证
if (!username) {
return errorMsg('用户名不能为空')
}
if (!password) {
return errorMsg('密码不能为空')
}
if (!password2) {
return errorMsg('确认密码不能为空')
}
if (password !== password2) {
return errorMsg('两次密码要一致')
}
return async dispatch => {
//发送注册的异步ajax请求
const response = await reqRegister({username, password, type})
const result = response.data
if (result.code === 0) {
// 分发成功的同步action
await getMsgList(dispatch,result.data._id)
dispatch(authSuccess(result.data))
} else {
// 分发错误提示信息的同步action
dispatch(errorMsg(result.msg))
}
}
}
```
3. 使用
```javascript
import {connect} from 'react-redux'
import {login} from '../../redux/actions'//action中的方法
export default connect(
state => ({user: state.user}),//reducers中定义的reducer
{login}//action中的方法
)('当前组件名')
```
4. 详情在redux_test_new文件夹中的readme
### 路由
1. 基本使用
1. 下载包和引入包
```jsx
npm install --save 'react-router-dom'
import {HashRouter,Switch,Route} from 'react-router-dom'
```
2. 明确好界面中的导航区、展示区
3. 导航区的a标签改为Link标签
```jsx
Demo
```
4. 展示区写Route标签进行路径的匹配
```jsx
```
5. 的最外侧包裹了一个BrowserRouter或HashRouter
2. 路由组件与一般组件
1. 写法不同: 一般组件:
路由组件:
2. 存放位置不同: 一般组件:components 路由组件:pages
3. 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:接收到三个固定的属性
- history:
- go: ƒ go(n)
- goBack: ƒ goBack()
- goForward: ƒ goForward()
- push: ƒ push(path, state)
- replace: ƒ replace(path, state)
- location:
- pathname: "/about"
- search: ""
- state: undefined
- match:
- params: {}
- path: "/about"
- url: "/about"
3. NavLink与封装NavLink
1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
4. Switch的使用
1. 通常情况下,path和component是一一对应的关系。
2. Switch可以提高路由匹配效率(单一匹配)。
5. 解决多级路径刷新页面样式丢失的问题
1. public/index.html 中 引入样式时不写 ./ 写 / (常用)
2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3. 使用HashRouter
6. 路由的严格匹配与模糊匹配
1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2. 开启严格匹配:
3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
7. Redirect的使用
1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2. 具体编码:
```jsx
```
8. 嵌套路由
1. 注册子路由时要写上父路由的path值
2. 路由的匹配是按照注册路由的顺序进行的
9. 向路由组件传递参数
1. params参数
- 路由链接(携带参数):详情
- 注册路由(声明接收):
- 接收参数:this.props.match.params
2. search参数
- 路由链接(携带参数):详情
- 注册路由(无需声明,正常注册即可):
- 接收参数:this.props.location.search
- 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3. state参数
- 路由链接(携带参数):详情
- 注册路由(无需声明,正常注册即可):
- 接收参数:this.props.location.state
- 备注:刷新也可以保留住参数
10. 编程式路由导航 借助this.prosp.history对象上的API对操作路由跳转、前进、后退 -this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
11. BrowserRouter与HashRouter的区别
1. 底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
- HashRouter使用的是URL的哈希值。
2. path表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3. 刷新后对路由state参数的影响
- (1).BrowserRouter没有任何影响,因为state保存在history对象中。
- (2).HashRouter刷新后会导致路由state参数的丢失!!!
4. 备注:HashRouter可以用于解决一些路径错误相关的问题。
### setState
- 写法 setState(stateChange,[callback])
1. 函数:this.setState(state => ({count:state.count + 1})
2. 对象:this.setState({count:this.state.count + 1})
3. callback回调函数可选,如果需要在setState后获取最新的状态数据,在callback函数中读取
4. 对象方式是函数方式的简写方式,如果新状态不依赖原状态,使用对象方式,如果依赖,使用函数方式
- 异步还是同步
1. react相关回调(生命周期/react事件回调):异步
2. 其他异步回调(定时器回调/原生事件监听回调/promise回调):同步
- 多次调用
1. 对象方式:合并更新一次状态,只调用一次render()更新界面 ---状态更新和界面更新都合并了
2. 函数方式:更新多次状态,但只调用一次render()更新界面 ---状态更新没有合并,但界面更新合并了
### Component
- 存在的问题:
1. 父组件重新render(),当前组件也会重新执行render(),即使没有任何变化
2. 当前组件setState(),重新执行render(),即使state没有任何变化
- 解决
1. 原因:组件的componentShouldUpdate()默认返回true,即使数据没有变化render()都会重新执行
2. 方法一:重写shouldComponentUpdate(),判断如果数据有变化返回true,否则返回false
3. 办法二:使用PureComponent代替Component React.Component ===> React.PureComponent
4. 说明:一般都使用PureComponent来优化组件性能
- PureComponent的基本原理
1. 重写shouldComponentUpdate()
2. 对组件的新/旧state和props中的数据进行浅比较(, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据),如果都没有变化就返回false,否则就返回true
3. 一旦componentShouldUpdate()返回false,不再执行用于更新的render()
### 路由组件的lazyLoad
```jsx
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
loading.....}>
```
### antd
1. 下载包
```shell
//npm
npm install antd --save
//yarn
yarn add antd
```
2. 引入
```jsx
import { DatePicker } from 'antd';
ReactDOM.render(, mountNode);
```
3. 按需加载和自定义主题
- 方法一
1. 下载依赖模块
```
//npm
npm install --save react-app-rewired customize-cra babel-plugin-import
//yarn
yarn add react-app-rewired customize-cra babel-plugin-import
```
2. 根目录下创建config-overrides.js文件
```javascript
const {override, fixBabelImports} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
```
3. 修改配置文件 package.json
```json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
```
4. 自定义主题
1. 下载依赖包
```
//npm
npm install less less-loader@5.0.0 --save
//yarn
yarn add less less-loader
```
2. 修改config-overrides.js
```javascript
const {override, fixBabelImports, addLessLoader} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {'@primary-color': '#1DA57A'},//主题色
}),
);
```
- 方法二(文档 https://github.com/DocSpring/craco-antd)
1. 下载craco和craco-antd(包括了less、babel-plugin-import和自定义主题的方法)
```
//npm
npm i -S @craco/craco craco-antd
//yarn
yarn add @craco/craco craco-antd
```
2. 根目录下创建craco.config.js文件
```javascript
const CracoLessPlugin = require('craco-antd');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
customizeTheme: {//这里可以直接自定义主题色
"@primary-color": "#1DA57A",
"@link-color": "#1DA57A"
},
//customizeThemeLessPath: path.join(//也可以创建一个less文件,将需要改变的变量写入
// __dirname,
// "src/style/AntDesign/customTheme.less"
//)
},
},
],
};
```
4. 3x迁移4x
1. 表单中,向父组件暴露form表单对象的方法(不看官方文档。。不看迁移的后果!!!!)
- 3x
```
使用Form.create()创建上下文 eg:export default Form.create()(组件名)
使用this.props.form就能获取到表单组件
父组件上setForm={(form)=>this.form=form}
组件初始化时调用this.props.setForm(this.props.form)
这样在父组件中就可以使用this.form来使用form的属性和方法了
```
- 4x
```
与3x不同,4x废弃了Form.create(),这样就不能直接使用this.props.form了,
但是可以使用在组件上使用ref
eg:
使用React.createRef()来获取form的实体
formRef = React.createRef();
后面操作与3x类似只是this.props.form换成了this.formRef
```
### 高阶函数、高阶组件和函数柯里化
- 高阶函数
1. 一类特别的函数
1. 接受函数类型的参数
2. 返回值是函数
2. 常见
1. 定时器:setTimeout()/setInterval()
2. Promise:Promise(() => {}) then(value=>{},reason=>{})
3. 数组遍历相关的方法:forEach()/filter()/map()/reduce()/find()/findIndex()
4. 函数对象的bind()
5. Form.create()()/getFieldDecorator()() //antd中被弃用 3。高阶函数更新动态,更加具有扩展性
- 高阶组件
1. 本质是一个函数
2. 接受一个组件(被包装组件),返回一个新的组件(包装组件),包装组件会向被包装组件传入特定属性
3. 作用:扩展组件的功能
4. 高阶组件也是高阶函数:接收一个组件函数,返回时一个新的组件函数
- 函数柯里化
1. 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
```jsx
class xxx extends React.Component {
state = {
username:'',
password:''
}
changeData = (dataType) => {
return (event) => {
this.setState({[dataType]:event.target.value})
}
}
handleSubmit = () => {
console.log(`你输入的用户名是${username},密码是${password}`);
}
render() {
return(
用户名:
密码:
)
}
```
### Hooks
- 简介 允许不适用class的情况下使用state以及其他的react特性
- 引入方法
```jsx
import react,{useState,useEffect,useRef} from 'react
```
- useState
1. State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
2. 语法: const [xxx, setXxx] = React.useState(initValue)
3. useState()说明:
- 参数: 第一次初始化指定的值在内部作缓存
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
4. setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
- useEffect
1. Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
2. React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
3. 语法和说明:
```jsx
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
```
4. 可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- useRef
1. Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
2. 语法: const refContainer = useRef()
3. 作用:保存标签对象,功能与React.createRef()一样
- react-redux
1. react-redux 中提供了 useStore(),useSelector(),useDispatch() 三种钩子,不推荐使用useStore()
2. 比较
1. class模式
```jsx
// 需要使用高阶函数包裹 才能调用react-redux中的state和方法
export default connect(
state=>({comments:state.comments}),//state就是一个comments数组
{addComment,delComment,getComments}
)(App)
```
2. 函数模式
```javascript
//直接使用方法引入,调用 useSelector 和 useDispatch 实现对react-redux中的state获取与修改
import {useDispatch, useSelector} from "react-redux";
```
3. useSelector
```jsx
import {useSelector} from "react-redux";
// useSelector(state => state.xxx)
const comments = useSelector(state=>state.comments)
```
4. useDispatch
```javascript
import {useDispatch} from "react-redux";
const dispatch = useDispatch()
dispatch(xxx) //xxx可为action中的方法
```
### Context
1. 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
2. 创建Context容器对象:const XxxContext = React.createContext()
3. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
子组件
4. 后代组件读取数据:
```js
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
{
value => ( // value就是context中的value数据
要显示的内容
)
}
```
5. 注意:在应用开发中一般不用context, 一般都它的封装react插件
### render props
#### 1. 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构, 一般用render函数属性
#### 2. children props
xxxx
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
#### 3. render props
}>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
### 错误边界
1. 理解:
错误边界:用来捕获后代组件错误,渲染出备用页面
2. 特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
3. 使用方式:
getDerivedStateFromError配合componentDidCatch
```js
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
```
### 组件通信方式总结
1. 方式:
1. props:
- children props
- render props
2. 消息订阅-发布:
pubs-sub、event等等
3. 集中式管理:
redux、dva等等
4. conText:
生产者-消费者模式
2. 组件间的关系
- 父子组件:props
- 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
### key的作用
1. 虚拟DOM中key的作用:
1. 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2. 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
1. 若虚拟DOM中内容没变, 直接使用之前的真实DOM
2. 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
2. 旧虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。 2.如果确定只是简单的展示数据,用index也是可以的。