1、虚拟DOM
-虚拟dom是一个用于表示真实dom节点的javaScript对象。
1.假设有一个标签为:
<div id="box" class="box">hello world</div>
2.那么真实的DOM表示方法为:
var demo= document.getElementById('box');
// demo就是真实dom节点,其本质也是一个对象,只是该对象有很多的属性,操作起来是昂贵的。
3.其虚拟dom的表示方法为
var vBox = {
tagName: 'div',
props: {
id: 'box',
class: 'box',
name: 'zhangsan'
},
children: ['hello world']
}
// vBox其实就是虚拟dom,是我们用原生的Object对象去描述一个真实dom节点的方式,该对象只有标签的一些关键属性。
4.封装一个可以生成虚拟dom的方法,可参考如下写法
// 生成虚拟dom
function createVdom(tagName, props, children){
const VDom = {
tagName,
props,
children
}
return VDom;
}
5.封装一个转换虚拟dom为真实dom的方法:
// 把虚拟dom转换成真实dom
function creatRdom(vDom){
const { tagName, props, children } = vDom;
// 创建出真实dom节点
const rDom = document.createElement(tagName);
// 给节点添加属性
if(props){
Object.keys(props).forEach(item => {
rDom.setAttribute(item, props[item]);
})
}
// 遍历children
children.forEach(item => {
if(typeof item === 'string'){
const text = document.createTextNode(item);
rDom.appendChild(text);
}else{
rDom.appendChild(creatRdom(item))
}
});
return rDom;
}
2、认识组件
组件是什么
react最核心的思想是将页面中任何一个区域或者元素都可以看做一个组件 component
--那么什么是组件呢?
组件指的就是同时包含了html、css、js、image元素的聚合体
使用react开发的核心就是将页面拆分成若干个组件,并且react一个组件中同时耦合了css、js、image,这种模式整个颠覆了过去的传统的方式
import React from 'react'
import ReactDOM from 'react-dom'
// 这里感觉又不习惯了?这是在用JSX定义一下react元素
const app = <h1>欢迎进入React的世界</h1>
ReactDOM.render(
app,
document.getElementById('root')
)
有状态组件(类组件)
--有状态组件又被称为容器组件或者聪明组件,它主要用来处理数据或者页面逻辑交互。它比无状态功能更加强大。类组件可以维护自身的状态变量,即组件的state。
--有状态组件组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
constructor(props){
super(props);
this.state={
titleWord:'hello world'
}
}
render () {
return (
// 注意这里得用this.props.name, 必须用this.props
<h1>欢迎进入{this.props.name}的世界</h1>
<h1>这里显示的是自身的状态{this.state.titleWord}</h1>
)
}
}
ReactDOM.render(
<App name="react" />,
document.getElementById('root')
)
无状态组件(函数组件)
--无状态组件又称变现性组件或者木偶组件,为何叫木偶组件?因为它只关心数据传递props,只能访问输入的 props,同样的 props 会得到同样的渲染结果,不会有副作用。
--在适合的情况下,我们都应该且必须使用无状态组件。无状态组件不像其他两种方法在调用时会创建新实例,它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部优化
--同样,由于没有实例化,所以无法访问组件this中的对象,例如:this.ref、this.state 等均不能访问
import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => <h1>欢迎进入{props.name}的世界</h1>
ReactDOM.render(
// React组件的调用方式
<App name="react" />,
document.getElementById('root')
)
Jsx组件(16.3之前)
React.createClass({
render () {
return (
<div>{this.props.xxx}</div>
)
}
})
蠢组件(PureComponnet)
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
class YourComponent extends PureComponent {
……
}
组件的嵌套和样式
--组件嵌套的方式就是将子组件写入到父组件的模板中去,且react没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入React的世界</h1>
)
}
}
class Content extends Component {
render () {
return (
<p>React.js是一个构建UI的库</p>
)
}
}
class App extends Component {
render () {
return (
<Fragment key="key值">
<Title />
<Content />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
--组件样式
1.行内样式(react推荐使用行内样式)
// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
<p style={{color:'red', fontSize:'14px'}}>Hello world</p>
2.使用class
// 其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是,`class`需要写成`className`(因为毕竟是在写类js代码,会收到js规则的现在,而`class`是关键字
<p className="hello" style = {this.style}>Hello world</p>
3.classnames第三方包
4.css-in-js是针对react编写的一套在js中写css样式的框架
3、数据交互
数据的挂载方式
状态(state)
--状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
render () {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button>
{
this.state.isLiked ? '❤️取消' : '🖤收藏'
}
</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
this.state
是纯js对象,在vue中,data属性是处理过的,更改data的数据的时候会触发数据的getter
和setter
,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState
。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = {
name: 'React',
isLiked: false
}
}
handleBtnClick = () => {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button onClick={this.handleBtnClick}>
{
this.state.isLiked ? '❤️取消' : '🖤收藏'
}
</button>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
setState
有两个参数,第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做updater
-
参数是对象
this.setState({ isLiked: !this.state.isLiked })
-
参数是方法
this.setState((prevState, props) => { return { isLiked: !prevState.isLiked } })
注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props
setState
是异步的,所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState((prevState, props) => {
return {
isLiked: !prevState.isLiked
}
}, () => {
console.log('回调里的',this.state.isLiked)
})
console.log('setState外部的',this.state.isLiked)
props
props
是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render () {
return (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.name}是一个构建UI的库</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title name="React" />
<Content name="React.js" />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
props.children
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入{this.props.children}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.children}</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title>React</Title>
<Content><i>React.js</i>是一个构建UI的库</Content>
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
组件之间的数据交互
组件状态(State)
受控和非受控组件(props)
4、生命周期
类组件的生命周期
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后)
1、初始化
在组件初始化阶段会执行
- constructor
官方建议不要在constructor
引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()
constructor(props) {
super(props);
this.state = {
isLiked: props.isLiked
};
}
- static getDerivedStateFromProps()
触发范围是和 16.4^ 是不同的,主要区别是在setState
和forceUpdate
时会不会触发
- 无条件的根据 prop 来更新内部 state,也就是只要有传入 prop 值, 就更新 state
- 只有 prop 值和 state 值不同时才更新 state 值。
class Table extends React.Component {
state = {
list: []
}
static getDerivedStateFromProps (props, state) {
return {
list: props.list
}
}
render () {
.... // 展示 list
}
}
- componentWillMount() / UNSAFE_componentWillMount()
-
componentWillMount()
将在React未来版本(官方说法 17.0)中被弃用. -
UNSAFE_componentWillMount()
在组件挂载前被调用,在这个方法中调用setState()
不会起作用,是由于他在render()
前被调用。
为了避免副作用和其他的订阅,官方都建议使用componentDidMount()
代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。
-
render()
render()
方法必须是一个纯函数,他不应该改变state
,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。
如果shouldComponentUpdate()
返回false
,render()
不会被调用。 -
componentDidMount()
componentDidMount
在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。
通常在这里进行ajax请求
如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom。比如图表
2、更新阶段
props
或state
的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
- componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
- 官方建议使用
getDerivedStateFromProps
函数代替componentWillReceiveProps
。当组件挂载后,接收到新的props
后会被调用。如果需要更新state
来响应props
的更改,则可以进行this.props
和nextProps
的比较,并在此方法中使用this.setState()
。 - 如果父组件会让这个组件重新渲染,即使
props
没有改变,也会调用这个方法。
- static getDerivedStateFromProps()
同上
- shouldComponentUpdate(nextProps, nextState)
在渲染新的props
或state
前,shouldComponentUpdate
会被调用。默认为true
。这个方法不会在初始化时被调用,也不会在forceUpdate()
时被调用。返回false
不会阻止子组件在state
更改时重新渲染。 - componentWillUpdate() / UNSAFE_componentWillUpdate()
在渲染新的state
或props
时,UNSAFE_componentWillUpdate
会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。
不能在这里使用this.setState(),也不能做会触发视图更新的操作 - render()
同上
- getSnapshotBeforeUpdate()
在reactrender()
后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。 - componentDidUpdate()
在更新发生后立即调用componentDidUpdate()
。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。
3、卸载阶段
- componentWillUnmount()
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount
中创建的任何监听。
4、错误处理
- componentDidCatch(error, info)
如果类组件定义了此生命周期方法,则它将成错误边界。在它中调用setState()
可以让你在下面的树中捕获未处理的JavaScript错误,并显示一个后备UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
函数组件的生命周期
函数组件没有具体的实例化
5、事件绑定
事件的写法
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
- 直接在render里写行内的箭头函数(不推荐)
- 在组件内使用箭头函数定义一个方法(推荐)
- 直接在组件内定义一个非箭头函数的方法,然后在render里直接使用
onClick={this.handleClick.bind(this)}
(不推荐)
Event对象
和普通浏览器一样,事件handler会被自动传入一个 event
对象,这个对象和普通的浏览器 event
对象所包含的方法和属性都基本一致。不同的是 React中的 event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
事件的参数传递
- 在
render
里调用方法的地方外面包一层箭头函数 - 在
render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递 - 通过
event
传递 - 比较推荐的是做一个子组件, 在父组件中定义方法,通过
props
传递到子组件中,然后在子组件件通过this.props.method
来调用
网友评论