React虚拟DOM概念虚拟DOM的结构
- 在传统的 Web 应用中,我们往往会把数据的变化实时地更新到用户界面中,于是每次数据的微小变动都会引起 DOM 树的重新渲染。如果当前 DOM 结构较为复杂,频繁的操作很可能会引发性能问题。React 为了解决这个问题,引入了虚拟 DOM 技术。
- 虚拟 DOM 是一个 JavaScript 的树形结构,包含了 React 元素和模块。组件的 DOM 结构就是映射到对应的虚拟 DOM 上,React 通过渲染虚拟 DOM 到浏览器,使得用户界面得以显示。与此同时,React 在虚拟的 DOM 上实现了一个 diff 算法,当要更新组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以在 React 中,当页面发生变化时实际上不是真的渲染整个 DOM
- React 虚拟 DOM 中的诸多如 div 一类的标签与实际 DOM 中的 div 是相互独立的两个概念,它是一个纯粹的 JS 数据结构,它只是提供了一个与 DOM 类似的 Tag 和 API。React 会通过自身的逻辑和算法,转化为真正的 DOM 节点。也正是因为这样的结构,虚拟 DOM 的性能要比原生 DOM 快很多。
HTML标签与React组件
- React可以直接渲染HTML类型的标签,也可以渲染React的组件
- HTML类型的标签第一个字母用小写来写表示。
import React from 'react';
//当一个标签里面为空的时候,可以直接使用自闭和标签
//注意class是一个JavaScript保留字,所以如果class应该替换成className
let divElement = <div className="foo"/>;
//等同于
let divElement = React.createElement('div',{className:'foo'});- React组件标签第一个字母大写。
import React from 'react';
class Headline extends React.component{
...
render(){
//直接return JSX语法
return <h1>Hello React</h1>
}
}
let Headline = <Headline />;
let headline = React.createElement(Headline);JSX语法使用第一个字母大小写来区分是一个普通的HTML标签还是一个React组件- 注意:因为
JSX本身是JavaScript语法,所以一些JavaScript中的保留字要用其他的方式书写,比如第一个例子中class要写成className
State属性state状态
state是组件内部的属性。组件本事是一个状态机,它可以在constructor中通过this.state直接定义它的值,然后根据这些值来渲染不同的UI。当state的值发生改变时,可以通过this.setState方法让组件再次调用render方法,来渲染新的UI。当state的值发生改变时,可以通过this.setState方法再次调用render方法,来渲染新的UI。
state设计原则
- 什么组件应该有
State,而且应该遵循最小化state的准则?那就是尽量让大多数的组件都是无状态的。为了实现这样的结构,因该尽量把状态分离在一些特定的组件中,来降低组件的复杂程度。最常见的做法就是创建尽力那个多的无状态组件,这些组件唯一要关心的事情就是渲染数据。而在这些组件的外层,应该有一个包含state的父级的组件。这个组件用于处理各种事件、交流逻辑、修改state、对应的子组件要关心的只是传入的属性而已 state应该包含什么数据?state中应该包含组件的事件回调函数可能引发UI更新的这类数据。在实际的项目中,这些应该是轻量化的JSON数据,应该尽量把数据的表现设计到最小,而更多的数据可以在render方法通过各种计算来得到。这里举一个例子,比如说现在有一个商品列表,还有一个用户已经选购的商品列表,最直观的设计方法如下:
{
goods:[
{
"id":1,
"name":"paper"
},
{
"id":2,
"name":"pencil"
}
...
],
selectedGoods:[
{
"id":1,
"name":"hello world"
}
],
}- 这样做当然可以,但根据最小化设计
state原则,还是有更好的方法!!! selectedGoods的商品就是goods里面的几项,数据是完全一致的,所以说这里只需要保存ID,就可以完成同样的功能。所以可以修改成如下。
selectedGoods:[1,2,3]- 在渲染这个组件的时候,只需要把要渲染的条目从goods中取出来就可以了。
state不应该包含什么数据?就像上面的例子所描述的一样,为了达到state的最小化,下面👇几种数据不应该包含在state中- 可以由state计算出的数据。就像selectedGoods一样,可以由goods列表计算得出。
- 组件。组件不需要保存到state中,只需要在
render方法中渲染。 props中的数据。props可以看作是组件的数据来源,它不需要保存在state中。
事件与数据的双向绑定
- bodyIndex.js代码
import React from 'react';
import BodyChild from './bodychild'
export default class BodyIndex extends React.Component {
constructor() {
super(); //调用基类的所有的初始化方法
this.state = {
username: "Parry",
age:20
};//初始化赋值
}
handleChildValueChange(event){
this.setState({age:event.target.value});//取出子页面的值
}
changeUserInfo(){
this.setState({age:50});
};
render() {
return (
<div>
<h2>页面主题内容</h2>
<p>{this.state.username} {this.state.age} {this.props.userid} {this.props.username}</p>
<p>age: {this.state.age}</p>
<input type="button" value="提交" onClick={this.changeUserInfo.bind(this)}/>
<BodyChild handleChildValueChange={this.handleChildValueChange.bind(this)}/>
</div>
)
}
}
- bodychild.js代码
import React from 'react';
export default class BodyChild extends React.Component{
render() {
return(
<div>
<p>子页面输入:<input type="text" onChange={this.props.handleChildValueChange}/></p>
</div>
)
}
}
- 通过在子页面
BodyChild设置props,子页面value改变调用handleChildValueChange,传值到父页面bodyIndex。也就是说在子页面中通过调用父页面传递过来的事件props进行组件间的参数传递。 - 思考(onChange与onBlur)的对比。
ES6的语法注意- 函数绑定方法this :
this.forceUpdateHander = this.forceUpdateHander.bind(this) - 或者调用时绑定:
onClick={this.changeUserInfo.bind(this,50)}
- 函数绑定方法this :
组件Refs(操作DOM的2⃣️两种方法)
- 第一种方式
var mySubmitButton = document.getElementById('submitButton');
console.log(mySubmitButton);
ReactDOM.findDOMNode(mySubmitButton).style.color = 'red';
//不推荐此方法,有安全隐患,XSS攻击- 第二种方法
console.log(this.refs.submitButton);
this.refs.submitButton.style.color = 'red';
独立组件间共享 Mixins
- ES6不支持Mixin,所以需要相插件来进行支持,
npm install --save react-mixin@2 - 测试一下Mixin是如何运行的
- 在
src/js/components下创建mixins.js
const MixinLog = {
componentDidMount(){
console.log("MixinLog componentDidMount");//查看Mixin生命周期
},
log(){
console.log("abcdefg");
}
};
export default MixinLog //向外输出
- 在bodyIndex.js中
import React from 'react';
import ReactDOM from 'react-dom';
import BodyChild from './bodychild';
import ReactMixin from 'react-mixin';
import MixinLog from './mixins';
changeUserInfo() {
...
MixinLog.log();
};
render() {
...
<input id="submitButton" ref="submitButton" type="button" value="提交" onClick={this.changeUserInfo.bind(this, 99)}/>
...
}
BodyIndex.defaultProps = defaultProps;
ReactMixin(BodyIndex.propTypes,MixinLog);- 点击页面上的提交按钮🔘在
console.log中会出现MixinLog componentDidMount和abcdefg
React 內联式样
- 通过header.js演示JSX样式控制,直接內联到标签中的style
import React from 'react';
export default class CompomentHeader extends React.Component{
render(){
const styleComponentHeader = {
header: {
backgroundColor: "#333333",
color: "#ffffff",
"padding-top": "15px",
paddingBottom: "15px"
}
//还可以定义其他的样式
}
return(
<header style={styleComponentHeader.header}>
<h1>这里是表头</h1>
</header>
)
}
}- 在
React上不是很适合此方法,hover等一些动画或者伪类,但在移动开发ReactNative中会常用。
采用原始引用方式
header添加为<header style={styleComponentHeader.header} className="smallFintSize">,并在index.html引用相关css- 不好在于污染全局
內联式样中的表达式
import React from 'react';
export default class CompomentHeader extends React.Component{
constructor(){
super();
this.state ={
miniHeader:false //默认加载的时候还是高(不是mini)的头部
};
};
switchHeader(){
this.setState({
miniHeader: !this.state.miniHeader //对state进行取反
});
};
render(){
const styleComponentHeader = {
header: {
backgroundColor: "#333333",
color: "#ffffff",
"padding-top": (this.state.miniHeader) ? "3px" : "15px",
paddingBottom: (this.state.miniHeader) ? "3px" : "15px"
},
//还可以定义其他的样式
};
return(
<header style={styleComponentHeader.header} className="smallFintSize" onClick={this.switchHeader.bind(this)}>
<h1>这里是表头</h1>
</header>
)
}
}CSS模块化
"babel-plugin-react-html-attrs": "^2.0.0"让JSX中className能变回原来class- webpack要重新配置
// webpack.config.js
var webpack = require("webpack");
var path = require("path");
module.exports = {
devtool: 'source-map',
context: path.resolve(__dirname, "src"),
entry: "./js/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: 'bundle.js' // 打包输出的文件
},
module: {
rules: [{
test: /\.js$/, // test 去判断是否为.js或.jsx,是的话就是进行es6和jsx的编译
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader',
//配置参数;
options: {
presets: ['es2015', 'react'],
plugins: ['react-html-attrs']
}
}, ]
},
{
test: /\.css$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}]
},
]
},
};
- 在
src/css下,创建一个footer.css,此css设置初衷是为了单独去渲染footer,希望footer.css不会污染全局,但通常情况下全局引用css是会污染全局的,内容如下
.miniFooter {
background: #333333;
color: #ffffff;
padding-left: 20px;
padding-top: 3px;
padding-bottom: 3px;
}
.miniFooter h1 {
font-size: 15px;
}- 在footer.js下写
import React from 'react';
var footerCss = require("../../css/footer.css");//引入css
export default class CompomentFooter extends React.Component{
render(){
console.log(footerCss);
return(
<footer class={footerCss.miniFooter}>//通过var footerCss 选取footer.css中miniFooter
<h1>这里是尾部</h1>
</footer>
);
}
}- 因为我们在打包时设置了
localIdentName:'[path][name]__[local]--[hash:base64:5]',这地方就是引用css的路径限制。 - 默认情况下,CSS 将所有的类名暴露到全局的选择器作用域中。样式可以在局部作用域中,避免全局作用域的样。详细🔎请查看官方文档官方文档式
- 所以在浏览器中
console出了Object {miniFooter: "css-footer__miniFooter--2W_7G"}