公司最近要做react的项目,所以,小白继续学习中……整理笔记如下……
React 和 Vue的对比
模块化:从代码的角度分析,把可复用的代码,抽离为单个的模块,便于项目的维护和开发。
组件化:从ui的角度分析,把可复用的ui抽离为单独的组件,便于项目的维护和开发。
vue的组件化
通过.vue文件,来创建对应的组件
- template 结构
- script 行为
- style 样式
react的组件化
没有像vue这样的组件模板文件。一切都是以js来表现。
虚拟DOM的概念
DOM -- Document Object Model
浏览器中的概念,用js对象来表示页面上的元素
虚拟DOM -- Virtual Document Object Model
框架中的概念,程序员用js对象来模拟页面上的DOM和DOM嵌套
目的:实现页面元素的高效更新
虚拟DOM为什么能高效更新?举个栗子……
需求:
点击日期和姓名旁的小三角,实现对应表格的数据排序
过程分析:
- 数据来源 -- 点击小三角,从数据库查询数据
- 数据存放 -- 查询到的数据,存放在浏览器的内存中,以对象数组的形式来表示
- 数据渲染 -- 循环拼接字符串 或 模板引擎
问题: 每次拿到数据后,内存中的对象数组是新的,页面总是要重新渲染。性能没有做到最优。
解决思路: 按需渲染。
如何实现按需渲染?
先了解网页的呈现过程:
- 浏览器请求服务器,获得HTML
- 浏览器在内存中,解析DOM结构,并在浏览器内存中,渲染出DOM树
- 浏览器把DOM树,呈现到页面上
因此,拿到内存中的新旧两棵DOM树,进行对比,就能得到需要按需更新的DOM元素
but,如何拿到新旧两棵DOM树呢?
浏览器中,没有直接提供获取DOM树的API,因此,只能程序员手动模拟DOM树
模拟对象:
<div id="ergou" title="fxd">
二狗有多哈
<p>非常哈</p>
</div>
模拟结果:
var div = {
tagName: 'div',
attrs: {
id: 'ergou',
title: 'fxd'
},
childrens: [
'二狗有多哈',
{
tagName: 'p',
childrens: [
'非常哈'
]
}
]
}
对比新旧模拟结果的js对象,即可实现页面元素的高效更新。这就是react中虚拟DOM的概念。
如何进行对比呢?
diff算法
diff -- different的缩写
- tree diff
新旧两棵DOM树,逐层对比的过程 - component diff
对比前后,如组件类型相同,暂时认定不需要更新
对比前后,组件类型不同,则移除旧组件,创建新组件,并追加到页面上 - element diff
如果是component diff中的情况1,则进行元素级别的对比
正式开始react的学习,准备工作,安装
webpack webpack-cli webpack-dev-server html-webpack-plugin
,搭建基本的项目结构,在package.json
中配置项目启动的具体参数,在webpack.config.js
中对项目插件进行配置
在项目中使用react
之前搭建的文件结构如下
- 先导入包,包的作用在入口文件中讲
import React from 'react'
import ReactDOM from 'react-dom'
- 在
index.html
文件中,挖坑,方便放入react创建的虚拟DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index</title>
<!-- <script src="./main.js"></script> -->
</head>
<body>
<h1>这是首页</h1>
<div id="app"></div> // 这里是坑
</body>
</html>
- 在入口文件
index.js
中写入
// 入口文件
import React from 'react' // 创建组件、虚拟DOM元素、生命周期
import ReactDOM from 'react-dom' // 把创建好的 组件 和 虚拟DOM 放到页面上展示
// 创建虚拟DOM元素
// 目标 <h1 title="啊,五环" id="myh1">你比四环多一环</h1>
// 参数1 - 字符串类型的参数,对应标签名
// 参数2 - 对象类型的参数,对应属性节点
// 参数3 - 子节点
// 参数4 - 关联的子元素
const myh1 = React.createElement(
'h1',
{
title: '啊,五环',
id: 'myh1'
},
'你比四环多一环'
)
const mydiv = React.createElement(
'div',
null,
'这是一个div',
myh1
)
// 渲染虚拟DOM元素
// 参数1 - 要渲染的虚拟DOM对象
// 参数2 - 指定容器,需要放容器的DOM对象
ReactDOM.render(mydiv, document.getElementById('app'))
JSX语法
// 导入包
import React from 'react'
import ReactDOM from 'react-dom'
// 创建虚拟DOM元素
// const mydiv = React.createElement('div', {id: 'mydiv', title: 'div aaa'}, '这是一个div元素')
// JSX的写法 - JS中,默认不能写类似于HTML的标记,所以要使用babel来转化这些JS中的标签
// JSX语法的本质,还是在运行的时候,被转化为了React.createElement形式来执行
const mydiv = <div id="mydiv" title="div aaa">这是一个div元素</div>
// 调用render函数渲染
ReactDOM.render(mydiv, document.getElementById('app'))
- 安装如下
babel插件
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
cnpm i babel-preset-env babel-preset-stage-0 -D
转换JSX语法的包
cnpm i babel-preset-react -D
- 根目录添加
.babelrc
文件,配置
{
"presets": ["env", "stage-0", "react"],
"plugins": ["transform-runtime"]
}
-
webpack.config.js
配置
module: { //要打包的第三方模块
rules: [
{ test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ }
]
}
语法及注意
import React from 'react'
import ReactDOM from 'react-dom'
let a = 10
let str = 'hello'
let boo = false
let title = 'FXD'
const hh = <h1>好好吃饭 做个好人</h1>
const arr = [
<h2>111111</h2>,
<h2>22222222222</h2>
]
const arrStr = ['11', '22', '33', '44']
// 所有节点,必须有唯一根元素进行包裹
ReactDOM.render(<div>
{a}
<hr/>
{str}
<hr/>
{boo ? 'z' : 'j'}
<hr/>
<p title={title}>pppp</p>
<hr/>
{hh}
<hr/>
{arr}
<hr/>
{/* 知识点: */}
{/* 1. 注释只能用此方法 */}
{/* 2. key存在的意义,是为了保持状态 - 多选框勾选时,新推入值,勾选项不发生改变 */}
{/* 3. key必须写在最外层 */}
{/* 4. 此处可用foreach在jsx外处理,区别,foreach不返回值,map要返回值,并且map必须写return */}
{arrStr.map(item => <div key={item}><h3>{item}</h3></div>)}
<hr/>
{/* className -- class */}
<p className="myclass">!!!!!!!</p>
<hr/>
{/* htmlFor -- for */}
<label htmlFor="ooo">1111</label>
</div>, document.getElementById('app'))
创建组件的两种方式
- 使用构造函数创建组件
import React from 'react'
import ReactDom from 'react-dom'
// 使用构造函数创建组件
function Fxd (props) {
// 必须返回合法的JSX虚拟DOM元素;return null -- 空组件
// return null
return <div>
这是hello组件
{/* 不论是vue,还是react,组件中的props只读,不能重新赋值 */}
-- {props.name}
-- {props.age}
-- {props.gender}
</div>
}
const ergou = {
name: 'fxd',
age: '26',
gender: 'female'
}
ReactDom.render(<div>
{/* <Fxd name={ergou.name} age={ergou.age} gender={ergou.gender}></Fxd> */}
{/* es6展开运算符的写法 */}
<Fxd {...ergou}></Fxd>
</div>, document.getElementById('app'))
将组件抽离成单独的
.jsx
文件
components
文件夹下的Fxd.jsx
import React from 'react'
export default function Fxd (props) {
return <div>
这是hello组件
-- {props.name}
-- {props.age}
-- {props.gender}
</div>
}
index.js
import React from 'react'
import ReactDom from 'react-dom'
import Fxd from './components/Fxd.jsx'
const ergou = {
name: 'fxd',
age: '26',
gender: 'female'
}
ReactDom.render(<div>
<Fxd {...ergou}></Fxd>
</div>, document.getElementById('app'))
配置:省略文件后缀名,设置@符号为项目根目录下的
src
文件夹
webpack.config.js
resolve: {
extensions: ['.js', '.jsx', '.json'],
alias: {
'@': path.join(__dirname, './src')
}
},
- 使用class关键字创建组件
es6中的class是实现面向对象编程的新形式
class的实例属性和静态属性,实例方法和静态方法
在class的{ }区间内,只能写构造器、静态方法、静态属性和实例方法;class是语法糖,本质还是用构造函数的原理实现的
function Person(name, age) {
this.name = name
this.age = age
}
Person.extra = 'aaaa'
// 实例方法
Person.prototype.say = function () {
console.log('我是person的实例方法')
}
// 静态方法
Person.say = function () {
console.log('我是person的静态方法')
}
const zs = new Person('zs', 3)
// 实例属性 -- 通过new出来的实例,访问到的属性
console.log(zs.name)
console.log(zs.age)
// 静态属性 -- 直接挂载给构造函数的属性
console.log(Person.extra)
zs.say()
Person.say()
// ------------------分割线--------------------------
console.log('---------------------------------------')
class Dog {
// 每个类中都有构造器,如果没有手动指定构造器,内部也会有个空构造器,类似于constructor(){}
// 每当new这个类时,优先执行构造器中的代码
constructor (name, age) {
this.name = name
this.age = age
}
// class内部,通过static修饰的属性,就是静态属性
static extra = '2222'
// 【实例方法】
say () {
console.log('我是dog的实例方法')
}
// 【静态方法】
static say () {
console.log('我是dog的静态方法')
}
}
const dh = new Dog('大黄', 1)
// 【实例属性】
console.log(dh.name)
console.log(dh.age)
// 【静态属性】
console.log(Dog.extra)
dh.say()
Dog.say()
extends实现子类继承父类
import React from 'react'
import ReactDom from 'react-dom'
// 父类,可以理解成原型对象prototype
class Chinese {
constructor (name, age) {
this.name = name
this.age = age
}
// 实例方法
sayHello () {
console.log('hello')
}
}
// 子类,使用extends关键字实现对父类的继承
class Scr extends Chinese {
}
const jjy = new Scr('jjy', 22)
console.log(jjy)
jjy.sayHello()
class Dbr extends Chinese {
// 1. 子类通过extends继承了父类,那么子类的constructor中,必须调用super()
// 2. 子类中的super就是父类中constructor构造器的引用
constructor (name, age, theater) {
super (name, age)
// 3. 在子类中,this只能在super后使用 | theater-子类独有的属性
this.theater = theater
}
}
const fxd = new Dbr('fxd', 24, 'jxl')
console.log(fxd)
fxd.sayHello()
ReactDom.render(<div></div>, document.getElementById('app'))
使用class创建组件的最基本结构
import React from 'react'
import ReactDom from 'react-dom'
class Hello extends React.Component {
render () {
return <div>
这是hello组件
</div>
}
}
ReactDom.render(<div>
<Hello></Hello>
</div>, document.getElementById('app'))
在class创建的组件中,使用
this.props
传值
import React from 'react'
import ReactDom from 'react-dom'
class Hello extends React.Component {
render () {
// 在class创建的组件中,如果想使用外界传递过来的props参数,直接通过this.props访问即可
// 在class内部,this表示当前组件的实例对象
return <div>
这是hello组件-{this.props.name}-{this.props.age}-{this.props.theater}
</div>
}
}
const user = {
name: 'fxd',
age: 22,
theater: 'jxl'
}
ReactDom.render(<div>
<Hello {...user}></Hello>
</div>, document.getElementById('app'))
有状态组件 和 无状态组件
有状态组件:使用class关键字创建的组件,有自己的私有数据(this.state
)和生命周期函数
无状态组件:使用构造函数创建出来的组件,只有props
,没有自己的私有数据和生命周期函数
本质区别:有无state
和有无生命周期函数
import React from 'react'
import ReactDom from 'react-dom'
class Hello extends React.Component {
constructor () {
super()
// 1. 只有调用了super(),才能使用this关键字
// 2. this.state = {} 相当于 Vue中的data(){return{}}
// 3. props是只读的,state是可以修改的
this.state = {
msg: '大家好,我是hello组件'
}
}
render () {
this.state.msg = 'msg的值已经修改了!!!'
return <div>
hello组件-{this.props.name}-{this.props.age}-{this.props.theater}
<div>{this.state.msg}</div>
</div>
}
}
const user = {
name: 'fxd',
age: 22,
theater: 'jxl'
}
ReactDom.render(<div>
<Hello {...user}></Hello>
</div>, document.getElementById('app'))
组件中props
和state/data
之间的区别
-
props
中的数据是外界传递过来的,state/data
中的数据是私有的(异步请求取回来的) -
props
中的数据只读,state/data
中的数据可读可写
小案例-评论列表
- 基础结构
index.js
import React from 'react'
import ReactDom from 'react-dom'
import CmtList from '@/components/CmtList'
ReactDom.render(<div>
<CmtList></CmtList>
</div>, document.getElementById('app'))
CmtItem.jsx
import React from 'react'
export default function CmtItem(props) {
return <div>
<h1>评论人: {props.user}</h1>
<p>评论内容: {props.content}</p>
</div>
}
CmtList.jsx
import React from 'react'
// 导入评论项子组件
import CmtItem from '@/components/CmtItem'
// class定义父组件
export default class CmtList extends React.Component {
constructor() {
super()
this.state = {
CommentList: [
{ id: 1, user: '张三', content: '哈哈,沙发' },
{ id: 2, user: '李四', content: '哈哈,板凳' },
{ id: 3, user: '王五', content: '哈哈,凉席' },
{ id: 4, user: '赵六', content: '哈哈,砖头' },
{ id: 5, user: '田七', content: '哈哈,楼下山炮' }
]
}
}
render() {
return <div>
<h1>评论列表</h1>
{this.state.CommentList.map(item => <CmtItem {...item} key={item.id}></CmtItem>)}
</div>
}
}
- 行内样式
render() {
return <div>
{/* jsx中,行内样式不能设置为字符串,应写为 style={ {color: 'red} } */}
{/* 在行内样式中,数值类型的样式,不用引号包裹,字符串类型的样式,要用引号包裹 */}
<h1 style={{ color: 'red', fontSize: '36px', zIndex: 3}}>评论列表</h1>
{this.state.CommentList.map(item => <CmtItem {...item} key={item.id}></CmtItem>)}
</div>
}
- 行内样式的封装
import React from 'react'
// 第一层封装
const itemStyle = { border: '1px solid #ccc', margin: '10px', padding: '10px', boxShadow: '0 0 10px #ccc'}
const userStyle = { fontSize: '14px' }
const contentStyle = { fontSize: '12px' }
// 第二层封装
const style = {
item: { border: '1px solid #ccc', margin: '10px', padding: '10px', boxShadow: '0 0 10px #ccc'},
user: { fontSize: '14px' },
content: { fontSize: '12px' }
}
export default function CmtItem(props) {
return <div style={style.item}>
<h1 style={style.user}>评论人: {props.user}</h1>
<p style={style.content}>评论内容: {props.content}</p>
</div>
}
- 抽离为单独的样式表模块
cnpm i style-loader css-loader -D
- 在
webpack.config.js
的rules中配置{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
- 在根目录下新建
css
文件夹,文件夹中创建cmtlist.css
CmtList.jsx
import React from 'react'
import CmtItem from '@/components/CmtItem'
// 导入组件所需样式
// 1.直接引入css样式表,默认是全局的
// 2.在vue中,使用<style scoped></style>来解决冲突
// 3.react中哦那个modules配置来解决冲突,具体参考webpack.config.js
import obj from '@/css/cmtlist.css'
console.log(obj)
export default class CmtList extends React.Component {
constructor() {
super()
this.state = {
CommentList: [
{ id: 1, user: '张三', content: '哈哈,沙发' },
{ id: 2, user: '李四', content: '哈哈,板凳' },
{ id: 3, user: '王五', content: '哈哈,凉席' },
{ id: 4, user: '赵六', content: '哈哈,砖头' },
{ id: 5, user: '田七', content: '哈哈,楼下山炮' }
]
}
}
render() {
return <div>
<h1 className={[obj.title, 'test'].join(' ')}>评论列表</h1>
{this.state.CommentList.map(item => <CmtItem {...item} key={item.id}></CmtItem>)}
</div>
}
}
CmtItem.jsx
import React from 'react'
import obj from '@/css/cmtlist.css'
export default function CmtItem(props) {
return <div className={obj.item}>
<h1 className={obj.user}>评论人: {props.user}</h1>
<p className={obj.content}>评论内容: {props.content}</p>
</div>
}
cmtlist.css
/* css模块化,只针对类选择器和id选择器生效,不会对标签选择器模块化 */
.title {
font-size: 40px;
color: red;
}
.item {
border: '1px solid #ccc';
margin: '10px';
padding: '10px';
box-shadow: '0 0 10px #ccc'
}
.user {
font-size: '14px'
}
.content {
font-size: '12px'
}
/* :global() 全局生效,className="类名"使用 */
/* :local() 被模块化的类名,className=[cssObj.类名]使用 */
:global(.test) {
font-style: italic
}
webpack.config.js
中module
中rules
配置
// path - 样式表相对于根目录的路径
// name - 样式表名称
// local - 样式类名定义名称
// hash:length - hash值,默认32位,一般取5~6位可防重复
{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]'] }
- 引用第三方样式的方法
- 自己的样式表,用
.scss
来写 - 第三方样式表是
.css
- 为自己的
.scss
文件,启动模块化即可 cnpm i sass-loader node-sass -D
-
webpack.config.js
中module
中rules
配置
{ test: /\.scss$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]', 'sass-loader'] }
为按钮绑定点击事件
import React, { Component } from 'react'
export default class BindClick extends React.Component {
constructor () {
super()
this.state = {}
}
render () {
return <div>
bindClick组件
<hr/>
{/* react中,事件名要用小驼峰,例如onClick */}
<button onClick={this.myClick}>按钮</button>
{/* onClick只接受function作为处理函数,箭头函数本身就是匿名的function函数 */}
{/* 箭头函数,用的最多的事件绑定形式,括号内可以传递参数 */}
<button onClick={ () => {this.myClick()}}>按钮</button>
</div>
}
myClick = () => {
console.log('222222222222')
}
}
// 原理 -- 为啥绑定的方法要用this调用
function Person () {
}
Person.prototype.say = function () {
}
Person.prototype.show = function () {
this.say()
}
修改state中的数据
- 修改方法
this.setState({
msg: '123'
})
- 两点注意
import React from 'react'
export default class BindClick extends React.Component {
constructor () {
super()
this.state = {
msg: '111',
name: 'zs',
age: 110
}
}
render () {
return <div>
bindClick组件
<hr/>
<button onClick={ () => {this.myClick('h', 'aa')}}>按钮</button>
</div>
}
myClick = (arg1, arg2) => {
// 1.在setState中,只会更新对应state的状态,不会覆盖其他state的状态
// 2.this.setState方法的执行,是异步的,this.setState({}, callback)
this.setState({
msg: '123' + arg1 + arg2
}, function () {
// 2.1这里打印,值变了
console.log(this.state.msg)
})
// 2.2这里打印,值没有变
console.log(this.state.msg)
}
}
- 数据双向绑定
import React from 'react'
export default class BindClick extends React.Component {
constructor () {
super()
this.state = {
msg: '111',
name: 'zs',
age: 110
}
}
render () {
return <div>
bindClick组件
<hr/>
<h2>{this.state.msg}</h2>
{/* 文本框绑定value以后,要么提供readOnly,要么提供onChange处理函数 */}
<input type="text" value={this.state.msg} onChange={(e) => this.textChanged(e)} ref="txt"/>
</div>
}
textChanged = (e) => {
// 1.通过事件参数e来获取
// const newVal = e.target.value
// this.setState({
// msg: newVal
// })
// 2.使用ref获取dom元素引用
const newVal = this.refs.txt.value
this.setState({
msg: newVal
})
}
}
生命周期
- 组件创建阶段 - 一次
componentWillMount
render
componentDidMount - 组件运行阶段 - props或者state改变时执行
componentWillReciveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate -
组件销毁阶段 - 一次
componentWillUnmount
-1小案例
index.js
import React from 'react'
import ReactDom from 'react-dom'
import Counter from '@/components/Counter'
ReactDom.render(<div>
<Counter initcount={100}></Counter>
<hr />
<Counter initcount={'猜猜有多少'}></Counter>
</div>, document.getElementById('app'))
counter.jsx
import React from 'react'
export default class Counter extends React.Component {
constructor() {
super()
this.state = {
}
}
render() {
return <div>
<h1>胖哥减肥打卡日记</h1>
<button>点击胖哥重量-1</button>
<h3>当前胖哥的重量:{this.props.initcount}kg</h3>
</div>
}
}
propTypes包
- 为外界传递过来的props属性值,做类型校验
- 若外界没有规定属性值,给默认的初始值
Counter.jsx
import React from 'react' // 在React v15.5版本前,类型校验和react是在一起的
import Types from 'prop-types' // 导入类型校验的包
export default class Counter extends React.Component {
// 1.类型校验 2.isRequired-必传
static propTypes = {
inintcount : Types.number.isRequired
}
// 3.默认值
static defaultProps = {
initcount : 100
}
constructor() {
super()
this.state = {
}
}
render() {
return <div>
<h1>胖哥减肥打卡日记</h1>
<button>点击胖哥重量-1</button>
<h3>当前胖哥的重量:{this.props.initcount}kg</h3>
</div>
}
}
生命周期函数
- 创建阶段的生命周期函数
import React from 'react'
import Types from 'prop-types'
export default class Counter extends React.Component {
static propTypes = {
inintcount : Types.number.isRequired
}
static defaultProps = {
initcount : 100
}
// 第一个生命周期函数
// 为组件初始化私有数据
constructor() {
super()
this.state = {
msg: "i'm msg"
}
}
// 第二个生命周期函数
// 类似于vue中的created,在此生命周期可以访问props和state以及发送ajax请求
componentWillMount () {
console.log(this.props.initcount)
console.log(this.state.msg)
this.show()
}
// 第三个生命周期函数
// 虚拟DOM正在被创建,当render执行完,虚拟DOM才创建到内存中
render() {
return <div>
<h1>胖哥减肥打卡日记</h1>
<button>点击胖哥重量-1</button>
<h3 id="myh3">当前胖哥的重量:{this.props.initcount}kg</h3>
</div>
}
// 第四个生命周期函数
// 组件已经渲染到页面上
componentDidMount () {
console.log(document.getElementById('myh3'))
}
show = () => {
console.log('嗨,我是show')
}
}
- 运行阶段的生命周期函数
更新state值
import React from 'react'
import Types from 'prop-types'
export default class Counter extends React.Component {
static propTypes = {
inintcount : Types.number.isRequired
}
static defaultProps = {
initcount : 100
}
constructor(props) {
// 在constructor中访问props,不能使用this.props.initcount,而要用参数接收
super()
this.state = {
weight: props.initcount
}
}
render() {
return <div>
<h1>胖哥减肥打卡日记</h1>
<button onClick={()=> this.substract()}>点击胖哥重量-1</button>
<h3 id="myh3">当前胖哥的重量:{this.state.weight}kg</h3>
</div>
}
// 第一个生命周期函数 - 组件是否需要被更新
// 按需更新页面,减少不必要的DOM渲染
shouldComponentUpdate (nextProps, nextState) {
// 获取最新的值,不能用this.state.weight,要从参数中获取
if (nextState.weight % 2 === 0){
return true
}
return false
}
// 第二个生命周期函数 - 组件将要被更新
componentWillUpdate () {
console.log(document.getElementById('myh3').innerHTML)
}
// 第三个生命周期函数 - 组件完成了更新
componentDidUpdate () {
console.log(document.getElementById('myh3').innerHTML)
}
substract = () => {
this.setState({weight: this.state.weight-1})
}
}
更新props中的值
index.js
import React from 'react'
import ReactDom from 'react-dom'
import LifeCycleRun from '@/components/LifeCycleRun'
class Wrapper extends React.Component {
constructor() {
super()
this.state = {
num: 99
}
}
render() {
return <div>
<button onClick={()=>this.add()}>小胖增肥专用按钮</button>
<br />
<LifeCycleRun initcount={this.state.num}></LifeCycleRun>
</div>
}
add = () => {
this.setState({
num: this.state.num + 1
})
}
}
ReactDom.render(<div>
<Wrapper></Wrapper>
</div>, document.getElementById('app'))
LifeCycleRun.jsx
import React from 'react'
import Types from 'prop-types'
export default class Counter extends React.Component {
static propTypes = {
inintcount : Types.number.isRequired
}
static defaultProps = {
initcount : 100
}
constructor(props) {
super()
this.state = {
weight: props.initcount
}
}
render() {
return <div>
<h3 id="myh3">当前胖哥的重量:{this.state.weight}kg</h3>
</div>
}
// 组件props改变时会被触发
// 用参数接收,不能用this.props.initcount
componentWillReceiveProps(nextProps) {
this.setState({
weight: nextProps.initcount
})
}
}
注意:this.setState只能在componentWillReceiveProps
、componentWillMount
、componentDidMount
中使用
子组件向父组件传值
子组件向父组件传值,用的是方法的调用
父组件CmtList.jsx
import React from 'react'
import CmtItem from '@/components/CmtItem'
import CmtBox from '@/components/CmtBox'
import obj from '@/css/cmtlist.css'
export default class CmtList extends React.Component {
constructor() {
super()
this.state = {
CommentList: [
{ id: 1, user: '张三', content: '哈哈,沙发' },
]
}
}
render() {
return <div>
<h1 className={[obj.title, 'test'].join(' ')}>评论列表</h1>
{/* 1. 传入方法 - 注意写法 */}
<CmtBox pushComment={this.pushComment}></CmtBox>
{this.state.CommentList.map(item => <CmtItem {...item} key={item.id}></CmtItem>)}
</div>
}
// 3.将子组件传来的值做处理,然后改变state中的值
pushComment = (cmtobj) => {
let arr = this.state.CommentList
arr.unshift(cmtobj)
this.setState({
CommentList: arr
})
}
}
子组件CmtBox.jsx
import React, { Component } from 'react'
export default class CmtBox extends Component {
render() {
return (
<div>
评论人 <input type="text" ref="user"/>
评论内容 <input type="text" ref="content"/>
<button onClick={() => this.postCmt()}>发表评论</button>
</div>
)
}
// 2. 在子组件绑定的事件中调用传入的方法this.props.pushComment
postCmt = () => {
// 获取值
const user = this.refs.user.value
const content = this.refs.content.value
// 检验值
if (user.trim().length === 0 || content.trim().length === 0) return alert('搞快写完!!')
// 传递值
const cmtobj = {user, content, id: Date.now()}
this.props.pushComment(cmtobj)
// 清空
this.refs.user.value = this.refs.content.value = ""
}
}
配置axios、发送get请求
- 安装
npm i axios
- 引入和挂载
index.js
import React from 'react'
import ReactDom from 'react-dom'
// 引入
import axios from 'axios'
// 挂载
React.Component.prototype.$http = axios
ReactDom.render(<div>
111
</div>, document.getElementById('app'))
- 使用发送get请求
import React, { Component } from 'react'
export default class TestAxios extends Component {
render() {
return (
<div>
<button onClick={() => this.sendRequest()}>发送请求</button>
</div>
)
}
sendRequest = () => {
this.$http('https://api.douban.com/v2/movie/in_theaters').then(res => {
console.log(res)
})
}
}
使用async、await优化请求
sendRequest = async () => {
const {data} = await this.$http('https://api.douban.com/v2/movie/in_theaters')
console.log(data)
}
发送post请求
写法1
sendRequest = () => {
// 1. ajax发起post请求时,Content-Type默认为text/plain; charset=utf-8
// 2. 一般服务端认为客户端发送的数据类型时 application/x-www-form-urlencoded
this.$http.post('http://39.106.32.91:3000/api/post', {
name: 'zs',
age: 18
}, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).then(res => {
console.log(res)
})
}
写法2
// 提交给服务器的对象,要拼成字符串
sendRequest = () => {
this.$http.post('http://39.106.32.91:3000/api/post', 'name=zs&age=18').then(res => {
console.log(res)
})
}
写法3
sendRequest = () => {
this.$http.post('/api/post', {
name: 'zs',
age: 18
}).then(res => {
console.log(res)
})
这种写法需要在index.js
中配置transformRequest
和baseURL
import axios from 'axios'
// 1. 挂载axios前配置
// 2. 作用 - 在发起post请求前,对发送给服务器的数据,做一层包装转换
axios.defaults.transformRequest = [function (data, headers) {
const arr = []
for (let key in data) {
arr.push(key + '=' + data[key])
}
return arr.join('&')
}]
// 3. 设置全局的baseURL
axios.defaults.baseURL = 'http://39.106.32.91:3000'
React.Component.prototype.$http = axios
将axios配置抽离为全局的配置文件
将配置代码放在globalConfig.js
,在index.js
中引入即可
promise
小案例 - 封装读文件的方法
需求:封装读文件的方法,并且把读取文件的内容,返回给调用者
const fs = require('fs')
// 方法封装:不替调用者决定处理结果,而应把方法调用的结果返回给调用者
// fs.readFile是异步的
function getContentByPath(path, callback) {
fs.readFile(path, 'utf-8', (err, dataStr) => {
if (err) return callback(err)
callback(null, dataStr)
})
}
getContentByPath('./files/2.txt', (err, data) => {
console.log(err)
console.log(data)
})
promise
目标:解决回调地狱的问题
打印结果:
-
Promise.prototype
中的.then()
,是为Promise
实例,指定【成功】或【失败】之后做的事情 -
Promise
就表示一个异步操作
用promise
重写读文件的方法
const fs = require('fs')
function getContentByPath(path) {
return new Promise(function (resolve, reject) {
fs.readFile(path, 'utf-8', (err, dataStr) => {
if (err) return reject(err)
resolve(dataStr)
})
})
}
// 用法1
getContentByPath('./files/2.txt').then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})
// 用法2
getContentByPath('./files/2.txt')
.then(res => {
console.log(res)
return getContentByPath('./files/1.txt')
})
.then(res => {
console.log(res)
return getContentByPath('./files/3.txt')
})
.catch(err => {
console.log(err.message)
})
使用async
和await
简化调用
async function getContent() {
console.log('正在执行异步')
const res1 = await getContentByPath('./files/1.txt')
console.log(res1)
const res2 = await getContentByPath('./files/2.txt')
console.log(res2)
console.log('异步执行完毕')
}
console.log('开始')
getContent()
console.log('结束')
模拟时间过滤器
globalConfig.js
React.Component.prototype.dateFormat = function(date, fmt) {
let d=new Date(date)
let o = {
"M+": d.getMonth() + 1, //月份
"d+": d.getDate(), //日
"h+": d.getHours() % 12 === 0 ? 12 : d.getHours() % 12, //小时
"H+": d.getHours(), //小时
"m+": d.getMinutes(), //分
"s+": d.getSeconds(), //秒
"q+": Math.floor((d.getMonth() + 3) / 3), //季度
"S": d.getMilliseconds() //毫秒
};
let week = {
"0": "/u65e5", "1": "/u4e00", "2": "/u4e8c", "3": "/u4e09", "4": "/u56db", "5": "/u4e94", "6": "/u516d"
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (d.getFullYear() + "").substr(4 - RegExp.$1.length));
}
if (/(E+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "/u661f/u671f" : "/u5468") : "") + week[this.getDay() + ""]);
}
for (let k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
}
return fmt
}
使用时
发布时间: {this.dateFormat(v.createdAt, 'yyyy/MM/dd HH:mm:ss')}
路由
对应关系
后端:url到后端处理函数之间的对应关系
前端:hash地址到组件之间的对应关系,window.onhashchange
可监听
路由配置
npm i react-router-dom -S
- 在
App.jsx
根组件中引入
import {HashRouter, Route, Link} from 'react-router-dom'
render() {
return <HashRouter>
<div>
app
</div>
</HashRouter>
}
- 使用时,
Link
创建路由链接,to
指定路由地址
<Link to="/home">home</Link>
App.jsx
import React, { Component } from 'react'
import {HashRouter, Route, Link} from 'react-router-dom'
// 引入组件
import Home from '@/components/Home'
import Movie from '@/components/Movie'
import T250 from '@/components/T250'
import About from '@/components/T250'
export default class App extends Component {
render() {
return <HashRouter>
<div>
<h3>App根组件</h3>
<Link to="/home">home</Link>
<Link to="/movie">movie</Link>
<Link to="/movie/t250">t250</Link>
<Link to="/about">about</Link>
<hr/>
{/* 路由规则 */}
{/* path-路径;component-组件;exact-精确匹配 */}
<Route path="/home" component={Home} />
<Route path="/movie" component={Movie} exact />
<Route path="/movie/:type" component={T250} exact />
<Route path="/about" component={About} />
</div>
</HashRouter>
}
}
在T250.jsx
中打印this.props
可以拿到type
对应的值
编程式导航
this.props.history.push('/about')
路由重定向
import {Redirect} from 'react-router-dom'
<Route exact path="/" render={() => <Redirect to="/home"/>} />
路由嵌套
以Ahout.jsx
为例
import React, { Component } from 'react';
import {Route, Link, Redirect} from 'react-router-dom'
import Tab1 from '@/components/tabs/Tab1'
import Tab2 from '@/components/tabs/Tab2'
class About extends Component {
render() {
return (
<div>
About
<hr />
<Link to="/about/tab1">tab1</Link>
<Link to="/about/tab2">tab2</Link>
<Route path="/about/tab1" component={Tab1} />
<Route path="/about/tab2" component={Tab2} />
<Route exact path="/about" render={() => <Redirect to="/about/tab1"/>} />
</div>
);
}
}
export default About;