# React 简述
用于构建用户界面的 JavaScript 库(只关注于View)、 由Facebook开源 英文官网: https://reactjs.org/ (opens new window) 中文官网: https://doc.react-china.org/
# React 的特点
**声明式编码:**以声明式编写 UI,可以让你的代码更加可靠,且方便调试。 **组件化编码:**组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离。 **支持客户端与服务器渲染:**可以使用 Node 进行服务器渲染,或使用 React Native (opens new window) 开发原生移动应用。
# React 为何高效
- 虚拟(virtual)DOM, 不总是直接操作DOM、1) DOM Diff算法, 最小化页面重绘
# React 知识点
# Diff 算法
# 虚拟 DOM
一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)。虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应。如果只是更新虚拟DOM, 页面是不会重绘的
# diff 算法的基本步骤
用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中,当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异,把差异应用到真实DOM树上,视图就更新了
把树形结构按照层级分解,只比较同级元素,给列表结构的每个单元添加唯一的 key 属性,方便比较,
React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字),选择性子树渲染。
开发人员可以重写shouldComponentUpdate 提高 diff 的性能。
# React 基本概念
# React 基本使用
# 相关JS库&插件
**react.js:**React的核心库 **react-dom.js:**提供操作DOM的react扩展库 **babel.min.js:**解析JSX语法代码转为纯JS语法代码的库 **React Developer Tools:**提供浏览器调试
页面引入
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
# 基础编码
<script type="text/babel"> //必须声明babel
// 创建虚拟DOM元素
const vDom = <h1>Hello React</h1> // 千万不要加引号
// 渲染虚拟DOM到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById('test'))
</script>
# 引入图片
import imgBase64 from './a.png'
const img = <img src={imgBase64} />
// or
const img = <img src={require('./a.png')} />
# 虚拟DOM
React提供了一些API来创建一种特别
的js对象,他可以将虚拟DOM元素渲染到页面中的真实容器DOM中显示
var element = React.createElement('h1', {id:'myTitle'},'hello')
# 纯JS创建虚拟DOM
React.createElement('h1', {id:'myTitle'}, title) // 一般不用
# JSX语法创建虚拟DOM
<h1 id='myTitle'>{title}</h1>
# JSX插入数组虚拟DOM
var li = [
<li key=1>jquery<li>,<li key=2>angular<li>,<li key=3>zeptoo<li>
]
var ul = <ul>{li}</ul>
# JavaScript XML
react定义的一种类似于XML的JS扩展语法: XML+JS,他专门用来创建react虚拟DOM(元素)对象,它不是字符串, 也不是HTML/XML标签。但它最终产生的就是一个JS对象。这个JS对象包含着这个DOM的创建信息
var ele = <h1>Hello JSX!</h1>
# JSX标签语法
// JSX标签语法跟HTML标签语法一样
var ele = <h2>Hello JSX!</h2> // <div>Hello JSX!</div>
var ele = <h2 class=''>Hello JSX!</h2> // <div id="">Hello JSX!</div>
# 语法编译规则
遇到 < 开头的代码, 以标签的语法解析在转换为html同名元素 遇到 { 开头的代码,以JS语法解析
# babel编译JSX
浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行,a. 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
不使用babel编译JSX语法创建虚拟DOM
const msg = 'I Like You!',myId = 'lixiaolong'
// 1. 创建虚拟dom
// React.createElement('标签名', {属性名:属性值},标签内容)
const vDom1 = React.createElement('h2', {id:myId.toLowerCase()},msg.toUpperCase())
// 2. 渲染虚拟dom插入#test1中
ReactDOM.render(vDom1,document.querySelector('#test1'))
I LIKE YOU!
// 1. 创建虚拟dom
const vDom2 = <h2 id={myId.toUpperCase()}>{msg.toLowerCase()}</h2>
// 2. 渲染虚拟dom插入#test2中
ReactDOM.render(vDom2,document.querySelector('#test2'))
i like you!
# 数据双向绑定
# 让元素值与状态数据绑定
- 绑定动态数据:(此时input的值是固定的)
- input绑定输入事件:
- 根据input的值改变动态数据:
# 生命周期钩子
# 生命周期流程(旧)
1. 第一次初始化渲染显示: ReactDOM.render()
constructor(): 创建对象初始化state
componentWillMount() : 将要挂载回调
ender() : 用于插入虚拟 DOM 回调
componentDidMount() : 已经挂载回调
2. 每次更新state: this.setSate()
componentWillUpdate() : 将要更新回调
render() : 更新(重新渲染)
componentDidUpdate() : 已经更新回调
3. 移除组件: ReactDOM.unmountComponentAtNode(containerDom)
componentWillUnmount() : 组件将要被移除回调
初始化创建 stage > 挂载前 > 挂载 > 挂载后
更新前 > 更新 > 更新后
卸载前 > 卸载后
# 常用钩子(旧)
render(): 初次挂载/更新渲染
componentDidMount(): 挂载后
componentWillUpdate(): 更新前
componentWillUnmount(): 卸载前
componentWillReceiveProps(): props 发生变化
# 新生命钩子
# 替换钩子
UNSAFE_componentWillUnmount(): 挂载前
UNSAFE_componentWillUpdate(): 更新前
UNSAFE_componentWillReceiveProps(): props 发生改变
# 新钩子
getDerivedStageFromProps(): props 派生状态(state)
getSnapshotBBeforeUpdate(): 保存数据快照(列表保存滚动高度)
# React 事件绑定
# 添加事件方式
class MyComponent2 extends React.Component {
render () {
return <div onClick={this.notice}>ES6类组件(复杂组件)</div>
}
notice= ()=>{
console.log('React事件触发')
}
}
# 剪切板事件
onCopy onCut onPaste // 事件名
DOMDataTransfer clipboardData // 属性
# 复合事件
onCompositionEnd onCompositionStart onCompositionUpdate // 事件名
string data // 属性
# 键盘事件
onKeyDown onKeyPress onKeyUp // 事件名
# 焦点事件
onFocus onBlur // 事件名
DOMEventTarget relatedTarget // 属性
# 表单事件
onChange onInput onInvalid onSubmit // 事件名
# 鼠标事件
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit // 事件名
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave // 事件名
onMouseMove onMouseOut onMouseOver onMouseUp // 事件名
# React 面向组件编程
# 模块与组件化概念
模块化:向外提供特定功能的js程序, 一般就是一个js文件。这样可以复用js, 简化js的编写, 提高js运行效率
组件化:一个界面的某个功能模块(html/css/js)
,这样可以复用编码, 简化项目编码, 提高运行效率
# 定义组件
# 工厂函数组件
工厂函数就是没有状态的组件
function MyComponent () {
return <div>工厂函数组件(简单组件)</div>
}
# ES6类组件
class MyComponent2 extends React.Component {
render () {
return <div>ES6类组件(复杂组件)</div>
}
}
# 渲染组件
ReactDOM.render(<MyComponent />, document.querySelector('#example'));
# 组件三大属性
# state 状态机
用于保存动态数据的一个容器,通常定义在constructor中
constructor(props) {
super(props)
this.state = {....} // 保存数据状态
}
# props 参数接收功能模块
用于外部接收参数的容器,通常在渲染组件时定义在标签中,内部拿取参数就在组件的this.props中,如果是简单组件,则在函数参数中
// 构造组件接收参数
function Person(props) {return <p>{props.name}</p>}
// 类组件接收参数
class Person extends React.Component {
render() {return <p>{this.props.name}</p>}
}
// 传递参数
ReactDOM.render(<Person name={'nmd'} />, ...);
# refs 标识获取元素
标记获取元素的容器,通常定义在虚拟DOM上,用于获取指定的虚拟DOM
// 标记获取元素
class Person extends React.Component {
render() return <p refs="content">{props.name}</p>}
showP() {...log(refs.content)}
}
// 标记获取元素
class Person extends React.Component {
render() return <p refs={p =>this.p = p>{props.name}</p>}
showP() {...log(this.p)}
}
# 组件拼接
组件拼接是一个父组件插入多个子组件。子组件也可以插入子组件。
// 定义父组件
class App extends React.Component {
// 父组件插入子组件
render() {return <div> <list /> </div>}
}
// 定义子组件
class List extends React.Component {render() {return <div>List</div>}}
# 功能组件编写流程(重要)
1. 拆分组件
2. 实现静态组件(只有静态界面,没有动态数据和交互)
3. 实现动态组件
1. 实现初始化数据动态显示
2. 实现交互功能
# 状态数据保存方向
看数据是某个组件需要(给他),还是某些组件需要(给共同的父组件)
# 子组件改变父组件状态
子组件中不能直接改变父组件的状态。数据状态在哪个组件,更新状态的行为就应该定义在哪个组件
# 脚手架搭建环境
安装create-react-app
脚手架模块
cnpm i create-react-app -g
运行命令生成项目基础结构
create-react-app react-demo
脚手架入口js基本设置(src/index.js)
//? 引入组件
import App from './components/app';
//? 渲染组件
ReactDOM.render(<App />, document.getElementById('root'));
运行开发环境:npm start
生产环境打包:npm build
#
# 子组件间通信
# props通信
# 通信流程
- 父组件定义状态数据 父组件定义改变状态数据方法
- 父组件传递状态数据给子组件B 父组件传递改变状态数据方法给子组件A
- 子组件A调用方法改变父组件状态数据
子组件B自动调用
componentWillReceiveProps()
方法并接收状态数据
# 消息订阅系统
# 通信流程
- 引入消息订阅系统
import PubSub from 'pubsub-js'
- 发布消息
PubSub.publish('消息名',data)
- 订阅消息(当消息发送改变时执行,并接收数据)
PubSub.subscribe('消息名',(msg, data){...})
# 路由组件编程
# 路由基本概念
# SPA
单页 Web 应用(single page web ),整个应用只有一个完整的页面,点击页面中的链接不会刷新页面, 本身也不会向服务器发请求,当点击路由链接时, 只会做页面的局部更新,数据都需要通过 ajax 请求获取, 并在前端异步展现
# react-router
react 的一个插件库,专门用来实现一个 SPA 应用,基于 react 的项目基本都会用到此库
# 后台注册路由
router.get(path, function(req, res))
当 node 接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来 处理请求, 返回响应数据
# 前端注册路由
<Route path="/about" component={About}>
当浏览器的 hash 变为#about 时, 当前路由组件就会变为 About 组件
# 路由组件流程
# 安装react-router
npm i react-router-dom --save
# 路由组件架构
components/app.jsx
// 引入定义路由功能组件
import { BrowserRouter, Redirect,
NavLink, Route, Switch
} from 'react-router-dom';
// 引入路由组件
import About from '../views/about'
import Home from '../views/home'
render {
return (
<BrowserRouter>{/* 1.定义路由管理框,如果父组件已经定义,那么子组件不需要定义 */}
<div>
{/* 2.定义路由链接 */}
<NavLink to='/about'>About</NavLink><br />
<NavLink to='/home'>Home</NavLink>
</div>
<div>
<Switch>{/* 3.定义路由显示区域 */}
<Route path='/about' component={About}/>
<Route path='/home' component={Home}/>
{/* 4.定义路由默认路径 */}
<Redirect to='/about'/>
{/* 4.定义根路径路由 */}
{/* <Route component={ Main }></Route> */}
</Switch>
</div>
</BrowserRouter>
)
}
# 路由传输数据
# 父路由传递数据
Msessagews.jsx
{/* 1.定义路由链接 */}
<NavLink to='/home/Msessagews/MsessagewsDatail/1'>{item.title}</NavLink>
<NavLink to='/home/Msessagews/MsessagewsDatail/2'>{item.title}</NavLink>
{/* 1.定义路由显示区域,并匹配一个占位符 */}
<Route path='/home/Msessagews/MsessagewsDatail/:id' component={MsessagewsDatail}/>
# 子路由接收数据
MsessagewsDatail.jsx
render() {
let { id } = this.props.match.params
return <div></div>
}
# 传递参数方式
# params 参数
路由链接(携带参数):`<Link to='/demo/test/tom/18'}>详情</Link>`
注册路由(声明接收):`<Route path="/demo/test/:name/:age" component={Test}/>`
接收参数:`this.props.match.params`
# search 参数
路由链接(携带参数):`<Link to='/demo/test?name=tom&age=18'}>详情</Link>`
注册路由(声明接收):`<Route path="/demo/test" component={Test}/>`
接收参数:`this.props.location.search`
备注:`获取到的 search 是 urlencoded 编码字符串,需要借助 querystring 解析`
# state 参数
路由链接(携带参数):`<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>`
注册路由(无需声明,正常注册即可):`<Route path="/demo/test" component={Test}/>`
接收参数:`this.props.location.state`
备注:`刷新也可以保留住参数`
# 路由自定义参数
# 路由链接类名自定义
function MyNavLink(props) {
return (<NavLink {...props} activeClassName='acc'/>);
}
...
<MyNavLink to='/about'>About</MyNavLink><br />
<MyNavLink to='/home'>Home</MyNavLink>
...
# 路由属性与方法
block() {} // 阻止?
createHref() {} // 创建链接
go() {} // 跳转链接
goBack() {} // 回退
goForward() {} // 前进
listen() {} // 听??
location:{hash: "", pathname: "/register", search: "", state…} // 路由信息
push() {} // 先进后出跳转
replace() {} // 先进先出跳转
}
# 类子组件使用路由 api
import { withRouter } from 'react-router-dom'
class Header extends Component {
// ...
render() {
this.props.goBack()
}
}
export default withRouter(Header)
# 两种路由形式的区别
BrowserRouter 与 HashRouter 的区别
- 底层原理不一样: BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。 HashRouter使用的是URL的哈希值。
- path表现形式不一样 BrowserRouter的路径中没有#,例如:localhost:3000/demo/test HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响 (1).BrowserRouter没有任何影响,因为state保存在history对象中。 (2).HashRouter刷新后会导致路由state参数的丢失!!!
- 备注:HashRouter可以用于解决一些路径错误相关的问题。
# Ant Design of React
antd
是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
安装手机版:npm install antd-mobile --save
安装PC版:npm install antd --save
# React ui库使用流程
import { Button } from 'antd-mobile'; // 引入指定标签
import 'node_modules/antd/dist/antd-mobile.css'; // 引入样式
ReactDOM.render(<Button />, mountNode); // 渲染
# 按需加载样式
1.安装对应插件
cnpm i react-app-rewired babel-plugin-import customize-cra --save
2.修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
}
3.根目录创建config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd-mobile',
style: "css",
})
);
4.直接引入标签
import { Button } from 'antd-mobile'
# Redux 集中式状态数据
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。它可以用在 react, angular, vue 等项目中, 但基本与 react 配合使用,作用: 集中式管理 react 应用中多个组件共享的状态
安装:cnpm i redux -D
# react-redux 基本架构
# 1.定义储存库
src/redux/reducers.js
//! 2. 定义储存库
//? 引入redux创建储存库(store)
import store from './store'
//? 引入方法常量命名
import {IN_CREMENT} from './action-types'
/* ε≡٩(๑>₃<)۶
! 定义储存库===>
! 第一个参数是储存库的类型,与储存库的值
! 第二个参数是储存库的行为, 用于定义改变储存值的方法
! action可以携带任意数据, 用于改变储存值状态
! 例:action.obj, action.data
! action.type是改变数据的方法类型
*/
const count = (state = 0, action)=>{
//? 定义方法行为
switch (action.type) {
case IN_CREMENT:
return state + action.data
default:return count
}
}
export default count
# 2.定义store包装函数
src/redux/store.js
//! 1. 定义store包装函数
//? 引入创建库方法 和 异步处理方法
import { createStore, applyMiddleware } from 'redux'
//? 引入异步处理中间件
import thunk from 'redux-thunk'
//? 引入开发者扩展工具支持包
import { composeWithDevTools } from 'redux-devtools-extension'
//? 引入储存库对象
import store from './redux/reducers.js'
/* (*^▽^*)
! 封装store回调
! 第一个参数的储存库
! 第二个参数是使用异步与第三方扩展的固定写法
*/
export default (store) =>createStore(
store,
composeWithDevTools(applyMiddleware(thunk))
)
# 3.定义行为常量名
src/redux/action-type.js
//! 3. 定义行为常量名, 这样可以防止写错单词。并且有语法提示
export const IN_CREMENT = 'IN_CREMENT'
# 4.定义通知改变函数
src/redux/actions.js
//! 4. 定义通知改变函数
//? 引入方法常量命名
import {IN_CREMENT} from './action-types'
//? 引入count储存库
//? 定义通知改变函数 in-->加 de-->捡
//? 返回一个通知类型函数,此方法由react-redux调用
export const inCrement = (number)=>({ type: IN_CREMENT, data: number })
# 5.渲染入口js中定义接口
src/index.js
//! 5. 定义react-redux接口
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containters/app';
//? 引入react-redux的 redux状态管理组件
import { Provider } from 'react-redux'
//? 引入储存库
import store from './redux/store'
//? App 封装在Provider(状态管理组件中)
//? 将储存库传入状态管理组件中
ReactDOM.render(
(<Provider {...store}><App /></Provider>),
document.getElementById('root')
);
# 6.定义组件接口容器
src/containters/app.jsx
//! 6.定义react-redux接口组件容器
//! 该容器用来包装Counter组件(主组件),
//! 并向其传入redux通知函数,与store
//? 引入连接函数
import {connect} from 'react-redux'
//? 引入 action 通知改变方法
import {inCrement} from '../redux/actions'
//? 引入主组件
import Counter from '../components/counter'
//? 向外暴露连接 App 组件的包装组件
export default connect(
state => ({count: state.count}), // 需要的数据(prop的key,val)
{inCrement}
)(Counter)
# 7.主组件使用数据与方法
src/components/counter.jsx
//! 7. 页面中定义引入类型,并使用储存库
import React, { Component } from 'react';
//? 引入限制传入类型方法库
import PropTypes from 'prop-types'
class Counter extends Component {
static propTypes = { // 定义静态对象 ==> App.PropTypes = {...}
count: PropTypes.number.isRequired, //? 必要,并且是一个数值
inCrement: PropTypes.func.isRequired, //? 必要,并且是函数
}
render() {
console.log(this.props.count) //==> 0
this.inCrement(6)
console.log(this.props.count) //==> 6
}
}
export default Counter
# react-redux 自定义架构
# 1.定义储存库
redux/reducers.js
//! 引入方法常量命名
import {IN_CREMENT, DE_CREMENT} from './action-types'
// 创建一个count储存库,并暴露出去
const count = (state=0, action)=>{
switch (action.type) {//? 定义改变数据的方法
case IN_CREMENT:
return state + action.data
case DE_CREMENT:
return state - action.data
default:return count
}
}
export default count
// 如果定义多个储存库,那么就用对象包起来
// export default {count,....}
# 2.定义储存库包装函数
redux/sotre.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import reducer from './reducers'
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
// 如果是多个储存库对象,则
// export default createStore(combineReducers(stores),composeWithDevTools(applyMiddleware(thunk)))
# 2.定义方法命名空间
redux/action-type.js
//? 加减方法
export const IN_CREMENT = 'IN_CREMENT'
export const DE_CREMENT = 'DE_CREMENT'
# 3.定义调用方法库
redux/actions.js
// 引入方法常量
import {IN_CREMENT, DE_CREMENT} from './action-types'
// 将方法暴露
export const inCrement = ()=>({ type: IN_CREMENT, data: number })
export const deCrement = ()=>({ type: DE_CREMENT, data: number })
# 4.定义redux接口
redux/index.js
import {stateSubStore, stateSubStoreAll} from 'react-redux-subscript'
import {count} from './store.js'
import {inCrement, deCrement} from './actions'
export const AppStateSus = stateSubStore({count},{
inCrement, deCrement
})
// (✪ω✪)绑定一个store
// export const stateSubApp = stateSubStore({stores}, {addComment, delComment, initComment})
// (ಥ_ಥ) 只需要方法
// export const stateSusApp = stateSubStore({store,need:false},{addComment})
// ψ(*`ー´)ψ绑定多个store, 并进行筛选需要的数据(可选)
/* export const AppStateSub = storePushToStateAll(
{ stores, filter:['comments'] },
{ delComment, initComment, addComment }
) */
# 5.react使用redux储存
//! 1.获取AppState绑定方法
import {AppStateSus} from '../redux'
class App extends Component {
constructor(props) {
super(props);
//! 绑定this.state数据
AppStateSus(this)
console.log(this.state.store) // 0
this.actions.inCrement(6)
console.log(this.state.store) // 6
}
render(){return<div></div>}
}
# redux异步解决方案
src/redux/actions.js
//! 异步执行方法
export const inCrementAsync = (number)=>(
dispatch => {
setTimeout(() => {
dispatch(inCrement(number))
}, 1000);
}
)
#
# redux 开发者工具
# 1.安装扩展插件
Redux DevTools
# 2.下载对应npm包
npm i redux-thunk redux-devtools-extension -D
# 3.定义储存库第二个参数
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import store from './redux/reducers.js'
// composeWithDevTools(applyMiddleware(thunk))
export default createStore(store, composeWithDevTools(applyMiddleware(thunk)) )
# 4.浏览器查看调试工具
# React 扩展 API
# setState
// 传入回调方式
setState((state) => ({count: this.count++}))
// 第二个参数为 state 更新后
setState({count: 12}, (state) => {state.count/* 12 */})
# lazyLoad
路由组件懒加载
// 1. 通过 React 的 lazy 函数配合 import() 函数动态加载路由组件 (路由组件代码会被分包)
const Login = lazy(() => import('@/pages/Loading'))
// 2. 通过异步组件<Suspense>指定在加载得到路由组件前, 显示自定义 loading 界面
const Component = <>
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx} />
<Redirect to="/login" />
</Switch>
</Suspense>
</>
# PureComponent
Component 有两个问题
- 只要执行 setState(),即使不改变状态数据,组件也会重新 render()
- 当前组件重新 render(),就会自动重新 render 子组件,即使没有父组件任何数据(效率低)
效率高的方法
只有当组件的 state 或 props 数据发生改变时才重新 render()
原因
Compoent 中的 shouldComponentUpdate() 总是返回 true
解决
# 方法 1
- 修改 shouldComponentUpdate 方法
- 比较新旧 state 或 props 数据, 如果有变化才返回 true, 如果没有返回 false
# 方法 2
- 使用 PureCompoent
- PureCompoent 修改了 shouldComponentUpdate 方法, 只有 state 和 props数据变化才返回 true
- 注意: 只是进行数据的浅对比, 如果是数据对象内部变化, 不会发生 render()
- 所以要 render() 生效, 需要产生新数据
# renderProps
const A = (props) => {
const name = '12312321'
return <div>{props.render(name)}</div>
}
const B = (props) => {
return <div>{props.name}</div>
}
const Component = () => {
return <A render={(name) => {<B name={name} />}}></A>
}
# ErrorBoundary
错误边界,在子组件发生错误时,处理并返回对应给用户的界面
注意:只能捕获子组件在周期内产生的错误,父组件和回调错误无法捕获
class Parent extends Component {
state = { error: null }
// 当 Parent 的子组件出现报错的时候
// 会触发 getDeiveStateFromError 调用, 并携带错误信息
// 返回一个 state, 将会更新 state
static getDeiveStateFromError(error) {
console.log('页面发生错误:', error)
return { error }
}
componentDidCatch() {
console.log('此处统计错误, 反馈给服务器, 用于通知程序员进行 bug 的解决')
}
render(props) {
const ErrorComponent = <h2>当前网络不稳定, 稍后再常识</h2>
return <div>{this.state.error ? ErrorComponent : ''}</div>
}
}
# React 组件通信方式总结
# 组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
# 几种通讯方式
1. props(children props, render props)
2. 消息订阅发布(pubs-sub, event)
3. 集中式管理(redux, dva, recoil....)
4. context(注入, 生产消费者模式)
# 搭配方式
父子组件: props
兄弟组件: 订阅发布, 集中式管理
祖孙组件(跨级): 订阅发布, 集中式管理, context(封装组件/插件比较多)