1、基本知识
1、直接js操作dom会导致浏览器重绘重排,频繁的操作dom会导致很大的性能消耗。
2、虚拟dom+diff算法只会操作改变的Dom;
3、babel 用于es6 => es5; js=>jsx;且script标签type值应为text/babel
4、虚拟dom本质是Object,只是供react内部使用,属性很少,而真实dom有很多属性,给浏览器绘制使用
5、虚拟dom会被react绘制成真实dom
6、jsx,js的扩展语法:js+xml
7、jsx花括号里接收表达式,表达式和语句的区别就是你拿个变量接能不能接;class =>className
2、类的基本知识
构造器中的this指向的是类的实例对象;
类中定义的方法是放在类的原型对象上,实例可通过原型链访问并使用;
类中定义的方法this指向调用的实例对象。
若子类继承父类,且子类中写了构造器,那么子类构造器中必须写super,帮助你取父类的属性;
类中定义的方法默认开启严格模式,所以直接调用时指向undefined
3、为什么react中的类组件中的事件需要绑定this
class Weather extend React.component {
constructor(props) {
// 构造器里的this指向实例对象
super(props);
this.state = {}
}
// 作为onClick的事件回调,调用该函数的不是实例对象,是直接调用,所以this是undefined(类中方法默认开启严格模式,所以也不指向window)
changeWeather() {
...
}
render(){
// render作为类的方法,在react的后台中new出来Weather实例对象调用,所以this也指向实例对象
return <h1 onClick={this.changeWeather}>test</h1>
}
}
解决方法: 1、在构造器中绑定this
constructor(props) {
// 赋值语句右右侧的changeWeather是顺着原型链找到的在类中定义的方法。
// 赋值语句的左侧是在实例对象中定义了changeWeather方法,所以原型和实例对象自身上都有这个方法了。
// 总结:我们在构造器中根据类中的方法,并创建了一个新的方法,并改变了他的this指向
this.changeWeather = this.changeWeather.bind(this);
}
2、在类中直接写赋值语句,类中直接写赋值语句,相当于给类的实例对象添加属性和方法
class Women extends React.Component {
// 给类的实例对象添加属性
sex = '女'
//赋值语句也可以赋值函数。
changeWeather = function() {
// 但是这样并没有解决问题,因为该函数作为事件还是直接调用,this还是不能指向实例对象
console.log(this)
}
// 我们可以通过箭头函数解决该问题
changeWeather = () => {
// 箭头函数没有this,他会找代码外侧环境的this,而外侧环境的this是实例对象。
console.log(this)
}
}
由上,我们可以简写state,
class Women extends React.Component {
// 原来初始化state
constructor(props) {
super(props);
this.state = {sex: '女'}
}
// 通过赋值初始化state
state = {sex: '女'}
}
4、其他注意项
onClick={demo()}会立即执行。因为react会执行render方法,那么在读到这一句时会对onClick进行赋值,赋值的结果就是右边的返回值。写了demo()就会执行该函数并拿到函数返回值undefined作为回调,所以不能写(),直接把函数作为onClick回调函数
setState通过合并实现更新。构造器并不更新,但rander更新一次就调一次;
5、props传递函数
父通过props传递一个函数,子在需要时调用即可。
6、prop批量传递
若需要传递多个属性给组件,可用对象解构赋值的形式传递,这是react提供的语法糖
let b = {name: 'x', age: 18}
console.log(...b) // 报错
<Persn {...b} /> // react语法糖
注意:对象不能通过展开运算符去展开对象,只能用来将对象的属性拷贝到新对象中去,如 a = {...b}。而react中{}是分隔符,表示里面的内容为表达式,所以在react标签上的...b其实是表达式,js本身并不支持扩展运算符像数组一样去展开对象,react通过某些途径实现了对象的展开
// 我们也可以通过展开运算符去批量接收入参
function sum(...number){
console.log(number); /// [1,2,3,4]
}
sum(1,2,3,4);
7、对props进行规则限制
对类自身增加propTypes属性
class Person extends React.Component {
...
}
// 加类型和必要性的限制
Person.propTypes = {
name:Proptypes.string.isRequired
speak:Proptypes.func, // 注意函数写法
}
// 加默认值
Person.defaultProps={
sex: '男'
};
// 但这样写不优雅,可以改成。加了static则表示给类加属性,不加表示给实例对象加属性
class Person {
static propTypes = {...} // 加类型和必要性的限制
static defaultProps = {...} // 加默认值
}
8、其他props的注意项
react中的构造函数在两种情况下需要声明:
1、初始化状态;
2、给事件绑定实例(this);
这两个情况我们都可以通过直接写赋值语句解决。所以构造器可写可不写。
什么情况下需要super(props)?
只有我们需要在构造器中使用this.props时,很少需要
函数式组件使用props直接传即可
9、refs就是用来获取dom节点的
1、string类型:给标签加ref属性可以通过this.refs获取到该节点(真实dom节点),就相当于给标签打上标签,方便后期使用。但是字符串的ref已经被官网废弃了,存在一些效率性的问题。
2、回调函数类型:
<input ref={(curNode)=>{this.input1 = curNode}} />
这种格式会导致回调函数在每次状态更新时都被调用,解决方法是如下(其实无所谓) 3、类绑定函数
setInput = (c) => {
this.input1 = c;
}
<input ref={this.setInput} />
4、createRef
// 在类中
// createRef调用后返回一个容器,该容器可以存储被ref所表示的节点
this.myRef = React.createRef()
// 在引用中
<input ref={this.myRef} />
10、高阶函数实现事件回调函数传参
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数, 1.若A函数接收的参数是一个函数,那么A就可以称之为高阶函数 2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数 常见的高阶函数,数组的reduce、map、Promise、setTimeout
实现思路:onChange事件回调是函数this.saveFormData的返回值,所以我们要以一个函数作为返回值,入参为event
function saveFormData (dataType) {
return (event) => {
this.setState({
[dataType]: event.detail.value
})
}
}
<input onChange={this.saveFormData('passWord')} />
11、柯里化
通过函数调用继续返回函数,实现多次接收参数最后统一处理的函数编码形式 如
function sum (a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
sum(1)(2)(3)
12、类组件生命周期 (旧)
API :
ReactDom.unmountComponentAtNode,手动卸载组件
挂载:
constructor // 构造器
componentWillMount // 组件将要挂载
render // 挂载中
componentDidMount 组件挂载完成
componentWillUnmount 组件将要卸载
更新:setState()
shouldComponentUpdate 状态是否应该更新(定义后需返回true or false) stateState执行后,控制组件更新的阀门。
componentWillUpdate 状态即将更新
render 更新时
componentDidUpdate
强制更新: forceUpdate()不改state状态,就要强制更新一下
componentWillUpdate 状态即将更新
render 更新时
componentDidUpdate 入参 preProps preState
父组件render
componentWillReceiveProps 组件将要接受prop时(第一次不算,父组件重新render时才算)
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
类组件生命周期(新)
去除componentWillMount componentWillUpdate componentWillReceiveProps
增加 getDerivedStateFromProps从props获取派生状态 入参 props,state 出参null或{}
用法:state在任何状态下都取决于props时可以使用,若返回object属性会替换掉state像相同属性,且state不可修改了
增加:getSnapshotBeforeUpdate 在更新前获取快照 入参 preProps preState 出参null或任何值
用法 在最近一次渲染输出(提交到dom节点)之前调用,改变之前可以从dom中获取一些信息,并作为参数传递给componentDidUpdate的第三个参数
componentDidUpdate(preProps preState,snapshotValue)
虚拟dom中的key
1、key的作用:
1)在旧的虚拟DOM中找与新虚拟DOM相同的key
若虚拟DOM中的内容没变,则直接返回之前的真实dom
若变化,则生成新的真实DOM替换之前的DOM
2) 若在旧虚拟DOM中未找到新虚拟DOM相同的key,则创建新的真实DOM渲染到页面
2、为啥不推荐index作为key
若对数据列表进行逆序添加、逆序删除等破坏顺序的操作,那index就无法对应原来的虚拟dom,就会产生没有必要的真实DOM更新。
若结构中还包含输入类的dom就会产生DOM更新错误。如下例子
this.state = {
arr: [
{id: 1, name: '小李'},
{id: 2, name: '小孙'},
]
}
add = () => {
let arr = this.state.arr;
this.setState({
arr: [{id: 3, name: '小王'},...arr]
})
}
<button onClick={add}>添加小王</button>
<ul>
{
this.state.arr.map((item,index) => {
return <li key={index}> {item.name} <input type="text">
})
}
</ul>
若我们先在li中的input框输入一些内容,然后点击添加按钮逆序添加一条数据,则内容会错乱。为什么呢?
由于我们使用index作为索引,react在经过diff算法时会发现 key为0 1 的虚拟dom都发生了变化,就会相应的更改真是dom。
但是input标签在虚拟dom看来并没有改变(虚拟dom中仅有很少的描述性的属性,像input的target.value这类属性是只有真实dom才有的,所以虚拟dom无法发现其变化),所以会被直接复用,就导致 0 1下的input还在原处未变化,但li里的文字内容改变了。这就产生了严重的bug。
react配置代理
1、跨域的原因
跨域的本质是因为浏览器的同源策略的限制,如果我们在localhost:3000向:5000发请求,ajax不会允许。准确来讲是允许发送,但不允许接收返回的数据,出的去回不来。
2、react的解决方法
客户端3000通过ajax向5000发送请求,ajax会限制。但我们可以通过代理的方式解决。 代理就是开在3000的一个很小的服务。客户端的请求会发给3000上的代理服务,由代理服务发给5000,然后5000再将结果告知3000的代理服务,代理服务再转发回3000的客户端。代理不会跨域,因为代理不是通过ajax引擎发送请求的,而产生跨域原因就是ajax引擎。
3、react 配置代理的方法
方法一:
在package.json中追加如下配置
```json
"proxy":"http://localhost:5000"
```
优点:配置简单,请求地址不需要加前缀
缺点:不能配置多个代理
工作方式:当请求了3000不存在的资源时,该请求会转发给5000
方法二:
1、创建代理配置文件
在src下创建配置文件: src/setupProxy.js,react会查找这个文件供webpack使用
2、编写具体代理规则
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { // 所有带有/api1前缀的请求都会转发给5000
target: 'http://localhost:5000', // 配置转发目标地址(能返回数据的服务器地址)
changeOrigin:true, // 控制服务器接收的请求头中的host字段值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址
})
)
}
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
跨组件传参 --消息订阅与发布
介绍pubsub.js
// 订阅消息
PubSub.subscribe('消息名称', (msg, data)=> {
// msg是消息名称,data是传递过来的数据
})
// 发布消息
PubSub.publish('消息名称', '这里是数据');
// 取消订阅
let token = PubSub.subScribe('消息名称', () => {})
PubSub.unsubScribe(token);
使用
在 componentDidMount 监听,在componentWillUnmount中取消监听
slot的实现
子组件的props.children里保存标签体的内容
// 父组件
<MyComponent>
hello
</MyComponent>
// MyComponent组件
console.log(this.props.children) // 'hello'
react路由
1、SPA的理解
single page app 单页面多组件
2、路由的理解
1、什么是路由:
路径和组件的映射关系
key是路径 value是function或组件
2、路由分类:
后端路由:
1)value是function,用来处理客户端提交的请求。
2)注册路由: router.get(path, function(req,res));
3)工作过程:当node接收到一个请求时,根据请求的路径匹配相应的路由,调用路由的函数处理请求并返回响应数据。
前端路由:
1)浏览器端路由,value是component,用于展示页面内容
2)注册路由: <Route path='/test' component={Test},就是在定于路由和组件的映射关系
3) 工作过程:当浏览器的path变为test时,找到Test组件并渲染
3、前端路由实现的原理
前端路由的实现是基于BOM的history,history像栈一样储存历史路由,并且可以检测到路由的变化。 history分为浏览器路由BrowserHisotry和HashHistory(锚点)
4、react-route-dom
1、一般组件和路由组件的最重要的区别
路由组件能收到关于路由器传递来的三个重要的props:history location match;
其他非组件路由也可以通过包裹withRoute组件来收到这些参数
2、多级路由刷新页面样式丢失的问题:
原因:刷新时重新读取样式文件时会在请求路径上增加多级路由,导致样式文件取不到而去取兜底的public下的index.html
解决方案:
html中引入文件不写相对路径,写绝对路径;或改成%PUBLIC_URL%
改用哈希路由:因为加了哈西路由后浏览器会认为#号后的值都是前端资源,会自动忽略#后面的值,所以刷新时也就不会加上那些值请求服务了
2、switch的作用 (v6改为Routes)
<Switch>是唯一的因为它仅仅只会渲染一个路径。相比之下(不使用<Switch>包裹的情况下),每一个被location匹配到的<Route>将都会被渲染。
3、模糊匹配和精准匹配
默认模糊匹配:匹配规则是输入的路径必须包含注册路径且顺序一致
// 注册路径
<Route path="/home" component={Home} >
// v6改为以下格式
<Route path="/home" element={<Home/>} >
// 输入路径
<link to="/home/a/b"> // 匹配
<link to="/a/home/b"> // 不匹配
精准匹配:注册路由增加exact属性
<link exact to="/home">
谨慎使用,会导致二级路由失效
3、redirect的使用
一般写在所有路由注册的最下方,当所有路由都没有匹配时则跳转redirect指定的路由
<Redirect to="/about"/>
v6改为Navigate,写法也有改变。
<Route path="/" element={<Navigate to="/home"/>} >
Navigate只要渲染就会引起路由的切换,如下,当number>2时跳转到about
```
return (
<div>
{
number > 2 ? <Navigate to="/about" /> :<h1>值不大于二</h1>
}
</div>
)
```
4、嵌套路由
注册子路由时需要写上父路由的path值;
路由匹配按照注册路由的顺序进行
v6中可以通过路由表 (见下文)或Route嵌套实现,但需要配合Outlet组件俩渲染子路由
Route嵌套
<Routes>
<Route path="/home" element={<Home>}>
<Route path="test" element={<Test>}></Route>
</Route>
</Routes>
// Home组件
(
<div>
// 此处展示子路由
<Outlet />
</div>
)
5、传递参数
传参方式:
query: ?a=1&b=2
params: /a/b
body: {urlencode, json}
一、params参数: <Link to='/demo/test/tom/18'></Link>
注册路由 声明接收: <Route path="/demo/test/:name/:age" component={Test}/>
接收参数:
const {name, age} = this.props.match.params;
// v6 新增
import {useParams} from 'react-router-dom'
let {id} = useParams;
二、传递search参数
路由链接 携带参数: <Link to='/demo/test?name=tom&age=18'></Link>
注册路由 无需声明,正常注册: <Route path="/demo/test" component={Test}/>
接收参数: const {search} = this.props.location
备注: 获取到的search是urlencoded编码字符串,需要借助queryString(react已经自动引入了该库)库解析(qs.parse)
// v6 新增
import {useSearchParams} from 'react-router-dom'
let [search, setSearch] = useSearchParams;
const name = search.get('name');
const age = search.get('age');
// 运行该函数会更新search参数
setSearch('name=david&age=17')
三、state参数
路由链接 携带参数: <Link to={{pathname:'/demo/test', state: {name: 'tom', age: 18}}}></Link>
注册路由 无需声明,正常注册: <Route path="/demo/test" component={Test}/>
接收参数: const {name, age} = this.props.location.state
备注: 刷新也可保留参数(因为用的是BrowserRouter,参数是保存在浏览器的history中的(window.history))
// v6新增
import {useLocation} from 'react-router-dom'
let {state} = useLocation(); // useLocation中就是v5中props.location中的值
8、replace模式开启
<Link replace to='/demo/test'></Link>
9、编程式路由导航
借助this.props.history对象上的API对路由进行操作。
push、replace、goBack、goForward、go等;
V6中实现编程导航还可以使用useNavigete
import {useNavigate} from 'react-route-dom'
function Demo(){
const {navigate} = useNavigate();
const changeRoute = () => {
// 第一种使用方式
navigate('/login', {
replace: false, // 默认false,
state: {a:1}
})
// 第二种使用方式,像history.go()一样前进后退
navigate(1);
navigate(-1);
}
}
10、withRouter
withRouter接受一个组件,返回值是一个新组件,给传入的组件提供路由组件所特有的API。
class Home extends React.component {
}
export default withRouter(Home)
11、BrowserRouter与HashRouter的区别
1)底层原理:
BrowserRouter是对H5的history API的二次封装,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值,不会向服务器发请求,而且会生成历史记录
2) path表现形式不一样
BrowserRouter里没有#号
3)刷新后对路由state参数的影响
BrowserRouter不会有影响,但hashRouter会丢失state
react-router-dom V6中的更新
1、路由表
import {userRoutes} from 'react-router-dom"
const element = useRoutes([
{
path: '/about',
element: <About/>,
children: [
{
path: 'name',
element: <Name>
}
]
},{
path: '/',
element: <Navigate to="/about"/>
}
]);
...
return (
<div>
{element}
</div>
)
搭配Outlet组件使用,有点像vue-router中的router-view标签
2、嵌套路由的使用:上面有写
3、Navilink删除了active 类名删掉了,改成了自定义class
<NaviLink to="/login" className=(isActive => isActive ? 'active' : '') />
增加了end属性, 在子路由匹配成功后,isActive停止匹配当前路由,isActive指自定义class函数参数isActive为false
4、增加了Navigate组件。见上文
5、useRoutes Hook 根据路由表创建Routes和Route。
6、useNavigete Hook 实现编程式导航,见上文;
7、useParams Hook 获取params参数,见上文;
8、useSearchParams Hook 获取search参数,见上文;
9、useLocation Hook 获取5.*中的props.location参数
10、useMatch Hook 获取5.*中的props.match参数
11、to的三种写法
to="/about" 在当前路径下跳转
to="./about" 不破坏当前路径的前提下跳转
to="about" ???
redux
三个核心概念
action
动作对象,共两个属性:type和data,type是派发事件的唯一标识,data是携带的数据
reducer
用于初始化状态、根据旧的状态和action产生新的状态。参数为preState和action;
reducer必须为纯函数,即:
1、不可以更改参数数据
2、不可有任何副作用,如网络请求,输入输出设备
3、不可调用Date.now Math.randon
4、相同的输入必须对应相同的输出
store
将state、action和reducer联系起来,并且有很多API,是redux的核心
基本流程
// 组件A派发action
import store from '/redux/store
import {createIncrementAction} from '/redux/_action
increment = () => {
store.dispatch(createIncrementAction(1))
}
// reducer 初始化状态以及产生新的状态。
const initState = 0
export default function countReducer(preState=initState, action) {
const { type, data } = action;
switch (type) {
case "increment":
return preState + data;
default:
return preState
}
}
// 创建store并传入reducer
import {createStore} from 'redux'
import countReducer from './count_reducer'
const store = createStore(countReducer);
export default store;
// 组件B使用state
import store from '/redux/store'
// 监听store变化,调用render渲染更新的数据(因为redux只负责管理状态,不驱动页面改变)
// !!后期使用react-redux后就不需要手动监听了,connect API生成的容器会为我们提供该功能
componentDidMount() {
store.subscribe(() => {
this.setState({});
});
}
//组件使用state
<div>{store.getState()}</div>
react-redux
概念
- 区分UI组件和容器组件。UI组件就是react组件,容器组件是react-redux使用connect API生成的用来连接redux和react组件的组件。
- 容器组件可以使用redux的API,并将状态以及操作状态的方法通过props传递给UI组件
API
connect
使用方法:
const ContainerComponent = connect(
state => ({name:state.name}), // connect第一个入参是一个函数,入参state,返回值作为props传递给UI组件
dispatch => ({
increment: (data) => dispatch(createIncrementAction(data))
decrement:(data) => dispatch(createDecrementAction(data))
}) // connect第二个入参也可以是一个函数,入参dispatch,返回值作为ptops方法传递给UI组件
)(UIComponent)
// 注意! 第二个参数可以精简成以下格式
const ContainerComponent = connect(
state => ({name:state.name}),
{
increment: createIncrementAction,
decrement: createDecrementAction
} // 第二个参数也可以为对象,key为暴露的方法名,value为action。这是API为我们提供的优化
)(UIComponent)
// UI组件使用
this.props.increment(1);
<div>{this.props.name}</div>
// 使用该容器组件的地方需传入store(后期用provider就不需要每个容器组件都传store了)
<ContainerComponent store={store}>
作用:生成容器组件
Provider
用于让所有后代组件接收store
// index.js
import store from '/redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
combineReducers (redux上的API)
import {createStore, combinerReducers} from redux
const allReducer = combinerReducers({
count: reducerOne,
person: reducerTwo
}) // 此处定义了store里的总对象
export default createStore(allReducer)
纯函数
在reducer中不可直接更改preState,会导致redux不认可这种改变,因为redux是根据指向的更改来判定是否有数据更改的。所以 preState.unshift(data)无效, return [data, ...preState]生效。
reducer必须为纯函数
纯函数概念:
1、同样的入参就会得到同样的输出;
2、不得改写参数,不会产生副作用(网络请求,输入输出设备),不能调用Date.now等不纯的方法。
使用react-redux的开发者工具
1、安装redux-devtools-extension 2、
// store.js
import {composeWithDevTools} from 'redux-devtools-extension
export defaule createStore(allReducer, composeWithDevTools())
补充文档
setState
1、setState更改状态是异步的。
2、setState第二个参数是一个回调,在state修改完成后执行
3、setState第一个参数有两种格式,对象和函数,函数接收两个参数 state和props;
lazyLoad
不适用懒加载会导致项目直接加载所有路由组件,若网速慢或者项目大时用户体验不佳。
懒加载的使用方法
// 引入lazy和Suspense
import React, { Component, lazy, Suspense } from "react";
// 用lazy包裹组件的引入
const About = lazy(() => import("./About"));
const Home = lazy(() => import("./Home"));
// 用Suspense包裹所有route,并为其提供加载中显示的组件
<Suspense fallback={<h1>1111</h1>}>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Suspense>
Hooks
1、state Hook
(1)state hook让函数也有state状态,并对状态数据进行读写操作
(2)语法: const [xxx, setXxx] = React.useState(initValue)
(3)说明:
参数:初始值
返回值: 包含两个元素的数组,0为状态,1为更新状态值的函数
(4) setXxx的两种用法
setXXX(newValue):参数为新的状态值。
setXxx(value => newValue):参数为函数,接收原状态,返回新状态
2、Effect Hook
useEffect(() => {
// 可以做任何事情
console.log(1111);
return () => {
// 在组件卸载时调用,以及每次触发effect(除了第一次)后也会x先调用return的函数,再调用上面的部分
}
}, []) // 第二个参数不传则在初始化以及任何更新时调用,传空数组则仅在初始化时调用,传某些state则仅在这些state更改时调用。可以当做componentDidMount,watch,componentDidUpdate
3、Ref Hook
用法:
const myRef = React.useRef();
<input ref={myRef} type="text">
Fragment
<Fragment></Fragment> // 不会被渲染成标签,不接受除key以外的任何属性
<></> // 不会被渲染成标签,不接收任何属性
Context
context主要解决多层组件传递props的问题,使用context就可以祖孙或者更深层的传递参数。
// context.js
// 先创建context,括号中可传入初始值。可单独定义在外面,然后各个组件再引入。
const MyContext = React.createContext(init);
export default MyContext
// A组件
import MyContxt from './context'
const {Provider} = MyContext; // 跟react-redux中的Provider传递store很像
render() {
return (
// 在需要引入数据的最外层包裹Provider组件,并通过value传递数据,B及B下的所有组件都可以拿到value了
<Provider value={userName: 'xxx'}>
<B/>
</Provider>
)
}
// B组件
// 假设B组件并不需要使用context里的数据,那么它也不需要做任何操作
...
render() {
return <C/>
}
// C组件
// 引入context
import MyContext from './contex';
...
// 在类上定义contextType属性,此属性可以让你使用 `this.context` 来获取context的值
static contextType = MyContext;
render() {
return <div>{this.context.userName}</div>
}
我们也可以通过传递函数的方式动态修改context里的值
// A组件
...
state ={
userName: 'xxx'
}
changeUserName = () => {
this.setState({
userName: 'www'
})
}
render() {
return (
<Provider value={userName: this.state.userName, changeUserName: this.changeUserName}>
<B/>
</Provider>
)
}
// C组件
...
render() {
return (
<div>{this.context.userName}</div>
<button onClick={this.context.changeUserName}>修改用户</button>
)
}
如果是函数式组件,无法在类中声明contextType,我们可以使用Context.Consumer
import MyContext from "./Context";
const { Consumer } = MyContext;
// C组件
...
return (
// consumer里包裹一个函数,函数入参即context传递的value
<Consumer>
{value => {
return (
<div>{value.userName}, 年龄:{value.userAge}</div>
)
}}
</Consumer>
)
Purecomponent
component的两个问题
1、只要setState即使不改变状态也会重新render
2、只要父组件render,子组件无论有没有使用父组件数据,都也会render
优化:
我们可以通过shouldComponentUpdate钩子优化:
shouldComponentUpdate(nextProps, nextState) {
// 对比新旧prop和state是否有变化,没变化则返回false;
}
但是自己实现对比比较麻烦,我们可以使用pureComponent组件,该组件会自动帮我们写shouldComponentUpdate里的对比逻辑来实现高效的render
class Demo extents React.PureComponent {
}
但是他有一个问题,若setState传入的对象与state指针相同,则pureComponent中的shouldComponentUpdate阀门会返回false,无法实现功能。
let obj = this.state; // 浅拷贝,指向的是同一个对象
obj.carName = 'obj'
this.setState(obj);
所以不要直接更改数据,要产生新数据。
renderProps实现方案
1、childrenProps,问题:B拿不到A中的数据
<A>
<B></B>
</A>
class A extend Component {
this.state = {name: '1'}
render () {
<div>
{this.props.children}
</div>
}
}
2、render props: 向A传递一个函数prop(自定义名称。一般叫render),该函数返回一个组件
class parent extent Component{
render() {
return (
<A render={(data) => <B data={data}/>} />
)
}
}
class A extend Component {
this.state = {name: '1'}
render () {
<div>
// 在A组件内调用传入的render方法,并可以传递参数。
// 那么在上面提到的函数中也就能接受到参数并传递给B了
{this.props.render(data)}
</div>
}
}
错误边界 ErrorBoundary
getDerivedStateFromError 从错误中获取衍生状态:在任何子组件发生错误时调用该钩子(适用于生产,本地还是会在页面报错)只适用于后代组件生命周期中产生的错误。
state = {
hasError: ''
}
static getDerivedStateFromError (error) {
return {hasError: error}
}
componentDidCatch渲染组件时出错时调用的钩子;一般可在此钩子中统计错误,反馈给后台;