❤生命周期
意义:组件的生命周期有助于理解组件的运行方式 完成更复杂的组件功能 分析组件错误原因
作用:需要在某个时间段执行的一段代码 利用生命周期来实现
- 创建时: 页面加载时触发时
constructor
创建组件时最先执行 : 用于初始化state 事件绑定thisrender
每次渲染内容就触发 : 渲染UI 不能在render中调用setState() 因为setState会更新State中的数据 更新数据会触发render就会重新渲染ui 地狱循环报错componentDidMount
渲染DOM页面结构后 : 发送ajax网络请求 执行DOM的一些操作
- 更新时: State数据变动时
render
重新渲染后触发的方法 :- New props 当组件接收新的数值变化时
- setState 当利用setState修改State数据时
- forceUpdate 强制刷新DOM结构时
componentDidUpdate
当组件更新后的dom结构渲染完成后:- 发送ajax网络请求
- DOM操作
- 在此钩子函数中要使用setState() 需要一个if条件来判断 是否再次渲染页面 不然会一直调用render方法渲染更新 一旦渲染更新又会触发这个钩子函数
- 判断的条件依照项目逻辑来定 例:判断更前前后的某个数据是否相同来决定是否更新
- 卸载时: 组件从页面中销毁时
componentWillUnmount
- 清理定时器或其他东西 作为清洁器使用
- 可以做一些性能的优化: 在请求发送时关闭了页面 断开请求连接
钩子函数: 生命周期中的钩子函数相当于对于外界的一个通知 通知在某个时间段触发了什么生命周期
内存泄露: 本应该销毁的数据没有被销毁 一直占用着空间 导致可用内存变少
内存溢出: 内存超出了最大的内存范围
生命周期流程图 ↓
最新生命周期流程图 ↓
shouldComponentUpdate:
作用: 根据返回值是true
还是false
来决定是否调用render方法 在render前执行 在调用一些刷新Dom结构方法后执行
实际运用场景: 判断和现有的是否相同 相同返回false 不相同返回true 执行render钩子函数重新渲染页面 一般用于做优化处理 减少不必要的渲染
语法:
shouldComponentUpdate(x,y) {
// 具有两个形参 第一个是前一次的状态 第二个最新的返回的状态 (this.state也是之前的状态)
// 根据一个条件判断是否渲染
const bl = x = y ? true : false
return bl/条件判断返回的值
}
props值发生变化或setState执行这个方法
复用组件的两种模式
复用的逻辑: 复用state状态 复用state状态的操作方法
render-props模式 和 高阶组件 HOC 是两种通过原生js实现功能的编码写法 并不是新的API
❤ render-props模式
核心思路:父组件给子组件传递一个回调函数 这个回调函数有返回值 返回需要展示的页面效果数据 子组件中不需要渲染页面内容 所以在render方法return的时候 直接调用回调函数 把相应的数据通过参数传递过去 和祖传通信Context
一样将数据传递出去 通过数据组成新的组件
使用场景:封装一些频繁使用的方法 例:发送请求
区别:添加了一个强大的子组件 可封装状态 和操作状态的方法
// Mouse类组件封装状态
class Mouse extends React.Component {
// 实现思路:将要复用的 state 和操作state的方法封装到一个组件中
state = {
// 1.初始化state 要被复用的state数据
x: 0,
y: 0
}
// 4. 注册事件处理程序
handleMouserMove = (e) => {
// 5. 通过e事件对象接受到返回的数值 再通过setState修改state数据
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 2. 监听鼠标移动事件
componentDidMount() {
// 3. 通过调用调用widonw上的addEventListener方法 当触发鼠标移动事件时 会调用事件处理函数
// 怎么拿到的坐标值?
widonw.addEventListener('mousemove', this.handleMouserMove)
}
// 6. 父组件return 出回调函数中的值 this.props.render(this.state)
render() {
return this.props.render(this.state)
}
}
// App组提供UI结构
// App组件接受Mouse组件return返回的值 来实现需要渲染的ui结构
class App extends React.Component {
render() {
return (
// 通过render回调函数mouse形参接收Mouse传递来的state参数
// return可以使用获取到的参数做一些事情
<div>
<h2>render props App组件下</h2>
<Mouse render={(mouse) => {
return (
<p>鼠标当前位置{mouse.x},{mouse.y}</p>
)
}
}
/>
</div>
)
}
}
// 渲染App组件
ReactDOM.render(<App />, document.getElementById('root'))
children代替render属性
优点:更改了之后语义化直观 更好理解 相当于返回的所有子节点
// 事件处理程序 state 鼠标监听事件省略...
render() {
// render并不是一个规定固定的名称 为了语义更加直观好理解 可以将render改为children
return this.props.children(this.state)
}
// App组件
// App组件接受Mouse组件return返回的值 来实现需要渲染的ui结构
class App extends React.Component {
render() {
return (
// 通过render回调函数mouse形参接收Mouse传递来的state参数
<div>
<h2>render props App组件下</h2>
<Mouse children={(mouse) => {
return (
<p>鼠标当前位置{mouse.x},{mouse.y}</p>
)
}
}
/>
</div>
)
}
}
代码优化
- 添加props校验
// 给children属性添加校验 让其变成必填项 不填就报错
Mouse.propTypes = {
children: PropTypes.func.isRequired
}
- 在生命周期卸载时 解除mousemove事件绑定 监听事件等…
// componentWillunmount钩子函数中 调用window中的解绑事件方法 解绑鼠标移动事件
componentWillunmount() {
window.removeEventListener('mousemove' , this.handleMouseMove)
}
❤ 高阶组件 HOC
作用:是一个函数 接受包裹要被包装的组件 返回被增强后的组件
和render-props的区别:一个是添加一个强大的父组件 一个是提供一个强大的函数去包裹组件
原理:在创建的高阶函数中 创建一个类组件并继承React.Component 通过高阶函数组件的形参接受传入要被加强的组件 在render方法中通过组件标签将包装经过处理的数据retrun出去 在retrun时可以传递组件中的state状态和props接受到的参数
总结核心思路:创建一个函数 通过这个函数对组件进行数据的返回
// 1.创建函数
// 约定创建函数时以with开头
// 传递的组件需要以大写字母开头 因为组件名称是要开头字母大写
function winthMouse(WrappedComponent) {
// 2.创建要被传递的组件
class Mouse extends React.Component {
// 2.1 要传递的类组件状态
state = {
x: 0,
y: 0
}
// 修改state数据
handelMouseMove = e => {
this.setState({
// clientX、clientY是事件对象中传递的数据
x: e.clientX,
y: e.clientY
})
}
// 2.2 使用componentDidMount钩子函数 监听鼠标移动事件的逻辑
componentDidMount() {
widow.addEventListener('mousermove',this.handleMouseMove)
}
// 2.3 优化 在卸载时解绑鼠标事件监听事件
componentWillUnmount() {
widow.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
// 2.4 通过WrappedComponent作为组件组件标签传递 state数据
return (
// ...展开运算符 传递高阶组件处理的数据state
// 在传递包装后的组件在向他的父组件通信时 props也需要展开传递
<WrappedComponent {...this.state} {...this.props} />
)
}
}
// 8.设置displayName 给返回组件添加独一无二名称
// WithMouse 这里要注意开头字母大写 函数名可开头小写 组件名开头需大写
// 调用getDisplayName方法 将组件传递给这个方法
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
// 3.返回这个组件
return Mouse
}
// 7.displaynName方法设置
function getDisplayName(WrappedComponent) {
// 当传递过来的组件有displayName就逻辑中断
// 否则就添加在调用这个组件时传入的形参组合和当前高阶组件名称组合
// 都没有就默认显示Component
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
// 4.要被包装的组件
const Position = props => (
// 在被包装的组件中
//
<p>鼠标当前作为 (x: {props.x}, y:{props.y})</p>
)
// 5.调用包装好的高阶组件函数
cosnt MousePosition = withMouse(Position)
// 6.只是提供渲染作用的App组件
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
{/* 渲染增强后的组件 */}
{/* 在mousePosition子组件和app父组件需要通信时传递参数 */}
<MousePosition name='xxx1' />
</div>
)
}
}
// 9.渲染组件
ReactDOM.render(<App />, document.getElementById('root'))
使用步骤:
- 创建以with开头的函数
function withxxx() {}
- 指定函数参数 以大写字母开头(作为要渲染的组件)
function withxxx(WrappedComponent) {}
- 在函数内部创建一个类组件 提供复用的逻辑代码 并返回
function withxxx(WrappedComponent) {
// 创建类组件
class xxx extends React.COMPONENT {
// 复用的逻辑代码...
render() {
return <WrappedComponent {...this.state} />
}
}
// 返回了类组件
return xxx
}
// 因为是函数组件包裹组件 请求会先发往函数组件 为了让包裹的组件也能拿到数据 所以使用...展开运算符
// 展开运算符:将一个数组转为用逗号分隔的参数序列
- 使用高阶组件包装组件并渲染
// 定义要被包装position组件 ...
// 要被包装的组件 position
const MousePosition = withxxx(position)
// 在APP或其他组件中渲染组件
<MousePosition />
设置displayName
作用:让代码在React工具中显示更加清晰
造成这样的原因:因为高阶组件函数中包裹的组件在使用时返回的名称都不会改变 React又默认使用组件名称作为渲染出来后的标签名称 之后会造成dom结构的不清晰无法辨认
解决方法:为了解决这一问题 通过在retrun之前 给组件设置一个displayName属性取一个别名
// 设置displayName 给返回组件添加独一无二名称
// WithMouse 这里要注意开头字母大写 函数名可开头小写 组件名开头需大写
// 调用getDisplayName方法 将组件传递给这个方法
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
// 当传递过来的组件有displayName就逻辑中断
// 否则就添加在调用这个组件时传入的形参组合和当前高阶组件名称组合
// 都没有就默认显示Component
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
传递poros
不传递props会让props丢失 高阶组件作为一个返回加强组件的通道 他并没有往下继续传递props 所以需要在高阶函数的标签上添加props的传递
实际运用场景:当被包装的组件作为某个组件的子组件 需要和他的父组件通信时 就需要传递
解决方法:在渲染WrappedComponent时 将state和this.props一起传递给组件
// 高阶函数中
<WrappedComponent {...this.state} {...this.props} />
// 被增强后的高阶组件标签上
<MousePosition a="1" />
React 原理 setState() √
setState() 属于异步方法 setState({}) = setState((state.props)=>{},()=>{})
简洁语法: 在使用setSate时 不能依赖前一个state的数值
this.setState({
// 修改的数据
x: xxx,
y: xxx
})
- 跟随在后的setSate() 方法 并不会马上拿到最新的状态值 因为是异步更新数据 不会立即执行 所以状态值并不会改变
- 调用多次setSate() 也只会触发一次render()重新渲染
复杂语法 回调函数 :解决不能立马拿到setState修改的状态值
// 第一次setState 省略...
// 第二次setState
this.setState((state,props) => {
// state 是第一次更改后的状态值
// props是上一次的状态值
return {
// 逻辑代码
count: state.count + 1
}
})
setSate第二个参数:在页面完成渲染后立即执行某个参数 与生命周期中的某个钩子函数效果类似 可以互相替代
// 在setSate中放置一个回调函数
this.setState((state,props) => {
// state 是第一次更改后的状态值
// props是上一次的状态值
return {
// 逻辑代码
count: state.count + 1
}
},() => {
// 状态更新后页面渲染后执行
// 进行dom元素的操作
document.title = '更新后的标题' + this.state.count
})
JSX转换过程
JSX是createElement()方法的语法糖 一种更为简化的语法
流程:JSX -> babel/preset-react -> createElement -> React 元素对象(虚拟dom)