1、 运行 cnpm i react react-dom -S
安装包
2、 在项目中导入两个相关的包:
// 1. 在 React 学习中,需要安装 两个包 react react-dom
// 1.1 react 这个包,是专门用来创建React组件、组件生命周期等这些东西的;
// 1.2 react-dom 里面主要封装了和 DOM 操作相关的包,比如,要把 组件渲染到页面上
import React from 'react'
import ReactDOM from 'react-dom'
3、使用JS的创建虚拟DOM节点:
// 2. 在 react 中,如要要创建 DOM 元素了,只能使用 React 提供的 JS API 来创建,不能【直接】像 Vue 中那样,手写 HTML 元素
// React.createElement() 方法,用于创建 虚拟DOM 对象,它接收 3个及以上的参数
// 参数1: 是个字符串类型的参数,表示要创建的元素类型
// 参数2: 是一个属性对象,表示 创建的这个元素上,有哪些属性
// 参数3: 从第三个参数的位置开始,后面可以放好多的虚拟DOM对象,这写参数,表示当前元素的子节点
// 这是一个div
var myH1 = React.createElement('h1', null, '这是一个大大的H1')
var myDiv = React.createElement('div', { title: 'this is a div', id: 'mydiv' }, '这是一个div', myH1)
4、 使用 ReactDOM 把元素渲染到页面指定的容器中:
ReactDOM.render(myDiv, document.getElementById('app'))
ReactDOM.render(‘要渲染的虚拟DOM元素’, ‘要渲染到页面上的哪个位置中’)
注意: ReactDOM.render() 方法的第二个参数,和vue不一样,不接受 “#app” 这样的字符串,而是需要传递一个 原生的 DOM 对象
1、如要要使用 JSX 语法,必须先运行 cnpm i babel-preset-react -D
,然后再 .babelrc
中添加语法配置"presets": ["env", "stage-0", "react"]
;
2. JSX语法的本质:还是以 React.createElement 的形式来实现的,并没有直接把用户写的 HTML代码,渲染到页面上;
3. 如果要在 JSX 语法内部,书写 JS 代码了,那么,所有的JS代码,必须写到 {} 内部;
4. 当 编译引擎,在编译JSX代码的时候,如果遇到了<
那么就把它当作 HTML代码去编译,如果遇到了 {}
就把 花括号内部的代码当作 普通JS代码去编译;
5. 在{}内部,可以写任何符合JS规范的代码;
6. 在JSX中,如果要为元素添加class
属性了,那么,必须写成className
,因为 class
在ES6中是一个关键字;和class
类似,label标签的 for
属性需要替换为 htmlFor
.
7. 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
8. 如果要写注释了,注释必须放到 {} 内部
Hello.jsx
import React from 'react'
// 在 function 定义的组件中,如果想要使用 props,必须先定义,否则无法直接使用
// 但是,在class定义的组件中,可以直接使用 this.props 来直接访问,不需要预先接收 props
export default function Hello(props) {
// console.log(props)
return haha --- {props.name}
}
Hello2.jsx
import React from 'react'
export default class Hello2 extends React.Component {
constructor(props) {
super(props)
// console.log(props)
this.state = {
msg: '这是 Hello2 组件的私有msg数据',
info: 'test**'
}
}
// 保存信息1: No `render` method found on the returned component instance: you may have forgotten to define `render`.
// 通过分析以上报错,发现,提示我们说,在 class 实现的组件内部,必须定义一个 render 函数
render() {
// 报错信息2: Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
// 通过分析以上报错,发现,在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
// 虽然在 React dev tools 中,并没有显示说 class 组件中的 props 是只读的,但是,经过测试得知,其实 只要是 组件的 props,都是只读的;
// this.props.address = '123'
// console.log(this.props)
return
这是 使用 class 类创建的组件
外界传递过来的数据是: {this.props.address} --- {this.props.info}
{this.state.msg}
}
changeMsg = () => {
// console.log('ok')
// 注意: 这里不是传统网页,所以 React 已经帮我们规定死了,在 方法中,默认this 指向 undefined,并不是指向方法的调用者
// console.log(this)
// 直接使用 this.state.msg = '123' 为 state 上的数据重新赋值,可以修改 state 中的数据值,但是,页面不会被更新;
// 所以这种方式,React 不推荐,以后尽量少用;
// this.state.msg = '123'
/* this.setState({
msg: '123'
}) */
this.setState(function (prevState, props) {
// console.log(props)
return {
msg: '123'
}
}, function () {
// 由于 this.setState 是异步执行的,所以,如果想要立即拿到最新的修改结果,最保险的方式, 在回调函数中去操作最新的数据
console.log(this.state.msg)
})
// 经过测试发现, this.setState 在调用的时候,内部是异步执行的,所以,当立即调用完 this.setState 后,输出 state 值可能是旧的
// console.log(this.state.msg)
}
}
{/* 1.1 在React中,如果想要为元素绑定事件,不能使用 网页中 传统的 onclick 事件,而是需要 使用 React 提供的 onClick /}
{/ 1.2 也就是说:React中,提供的事件绑定机制,使用的 都是驼峰命名,同时,基本上,传统的 JS 事件,都被 React 重新定义了一下,改成了 驼峰命名 onMouseMove /}
{/ 2.1 在 React 提供的事件绑定机制中,事件的处理函数,必须直接给定一个 function,而不是给定一个 function 的名称 /}
{/ 2.2 在为 React 事件绑定 处理函数的时候,需要通过 this.函数名, 来把 函数的引用交给 事件 */}
1、用构造函数创建出来的组件:专业的名字叫做“无状态组件”
2、用class关键字创建出来的组件:专业的名字叫做“有状态组件”
用构造函数创建出来的组件,和用class创建出来的组件,这两种不同的组件之间的本质区别就是:有无state属性!!!
1、可以在webpack.config.js中为css-loader启用模块化:
css-loader?modules&localIdentName=[name]_[local]-[hash:8]
2、使用:global()
定义全局样式
webpack.config.js文件:
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.join(__dirname, './src/main.js'),
output: {
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
},
plugins: [ // 插件
new htmlWebpackPlugin({
template: path.join(__dirname, './src/index.html'),
filename: 'index.html'
})
],
module: {
rules: [
// 通过 为 css-loader 添加 modules 参数,启用 CSS 的模块化
{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[name]_[local]-[hash:5]'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(png|gif|bmp|jpg)$/, use: 'url-loader?limit=5000' },
{ test: /\.jsx?$/, use: 'babel-loader', exclude: /node_modules/ }
]
}
}
.babelrc文件:
{
"presets": ["env", "stage-0", "react"],
"plugins": ["transform-runtime"]
}
main.js
// JS打包入口文件
// 1. 导入 React包
import React from 'react'
import ReactDOM from 'react-dom'
// 使用JS语法,创建虚拟DOM元素
// var myDiv = React.createElement('h1', { id: 'mydiv', title: 'ok' }, '这是一个H1')
var myDiv = OKOKOK
// 在使用 Hello 组件之前,先导入 组件
import Hello from './components/Hello.jsx'
import Hello2 from './components/Hello2.jsx'
ReactDOM.render(
, document.getElementById('app'))
注意: 以上两种创建组件的方式,有着本质上的区别,其中,使用 function 构造函数创建的组件,内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据;使用 class 关键字 创建的组件,内部,除了有 this.props 这个只读属性之外,还有一个 专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的!
基于上面的区别:
使用 function 创建的组件,叫做【无状态组件】;
使用 class 创建的组件,叫做【有状态组件】
有状态组件和无状态组件,最本质的区别,就是有无 state 属性;同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数;
问题来了:什么时候使用 有状态组件,什么时候使用无状态组件呢?
1、 如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件;
2、如果一个组件,只需要根据外界传递过来的 props,渲染固定的 页面结构就完事儿了,此时,非常适合使用 function 创建出来的 无状态组件;(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一点)
列表及样式demo
CommentList.jsx
import React from 'react'
// 导入当前组件需要的子组件
import CommentItem from './CommentItem.jsx'
// 评论列表组件
export default class CommentList extends React.Component {
constructor(props) {
super(props)
// 定义当前评论列表组件的 私有数据
this.state = {
cmts: [
{ user: '张三', content: '沙发' },
{ user: '张三2', content: '板凳' },
{ user: '张三3', content: '凉席' },
{ user: '张三4', content: '砖头' },
{ user: '张三5', content: 'test' }
]
}
}
// 在 有状态组件中, render 函数是必须的,表示,渲染哪些 虚拟DOM元素并展示出来
render() {
//#region 循环 评论列表的方式1,比较low,要把 JSX 和 JS 语法结合起来使用
/* var arr = []
this.state.cmts.forEach(item => {
arr.push({item.user}
)
}) */
//#endregion
return
评论列表案例
{/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */}
{this.state.cmts.map((item, i) => {
// return
return
})}
}
}
CommentItem.jsx
import React from 'react'
// 注意: 在使用 import 的时候,import 只能放到模块的 开头位置
import inlineStyles from './cmtItemStyles.js'
// 导入评论项的样式文件【这种直接 import '../路径标识符' 的 CSS 导入形式,并不是模块化的CSS】
// import '../../css/commentItem.css'
// 默认情况下,如果没有为 CSS 启用模块化,则接收到的 itemStyles 是个空对象,因为 .css 样式表中,不能直接通过 JS 的 export defualt 导出对象
// 当启用 CSS 模块化之后,导入 样式表得到的 itemStyles 就变成了一个 样式对象,
其中,属性名是 在样式表中定义的类名,属性值,是自动生成的一个复杂的类名(防止类名冲突)
import itemStyles from '../../css/commentItem.css'
console.log(itemStyles)
// 封装一个 评论项 组件,此组件由于不需要自己的 私有数据,所以直接定义为 无状态组件
export default function CommentItem(props) {
// 注意: 如果要使用 style 属性,为 JSX 语法创建的DOM元素,设置样式,不能像网页中那么写样式;而是要使用JS语法来写样式
// 在 写 style 样式的时候,外层的 { } 表示 要写JS代码了,内层的 { } 表示 用一个JS对象表示样式
// 注意: 在 style 的样式规则中,如果 属性值的单位是 px, 则 px 可以省略,直接写一个 数值 即可
//#region 样式优化1
/* const boxStyle = { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 }
const titleStyle = { fontSize: 16, color: "purple" }
const bodyStyle = { fontSize: 14, color: "red" } */
//#endregion
//#region 样式优化2 把 样式对象,封装到唯一的一个对象中
/* const inlineStyles = {
boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
titleStyle: { fontSize: 16, color: "purple" },
bodyStyle: { fontSize: 14, color: "red" }
} */
//#endregion
/* return
评论人:{props.user}
评论内容:{props.content}
*/
// 注意: 当你怀念 vue 中 scoped 指令的时候,要时刻知道 , react 中并没有指令的概念
return
评论人:{props.user}
评论内容:{props.content}
}
cmtItemStyles.js
// 导入一个 样式的对象
export default {
boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
titleStyle: { fontSize: 16, color: "purple" },
bodyStyle: { fontSize: 14, color: "red" }
}