react学习笔记 - 知识点

公司最近要做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为什么能高效更新?举个栗子……

我是栗子

需求:
点击日期和姓名旁的小三角,实现对应表格的数据排序

过程分析:

  1. 数据来源 -- 点击小三角,从数据库查询数据
  2. 数据存放 -- 查询到的数据,存放在浏览器的内存中,以对象数组的形式来表示
  3. 数据渲染 -- 循环拼接字符串 或 模板引擎

问题: 每次拿到数据后,内存中的对象数组是新的,页面总是要重新渲染。性能没有做到最优。
解决思路: 按需渲染。

如何实现按需渲染?

先了解网页的呈现过程:

  1. 浏览器请求服务器,获得HTML
  2. 浏览器在内存中,解析DOM结构,并在浏览器内存中,渲染出DOM树
  3. 浏览器把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

之前搭建的文件结构如下


  1. 先导入包,包的作用在入口文件中讲
import React from 'react'
import ReactDOM from 'react-dom' 
  1. 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>
  1. 在入口文件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'))
  1. 安装如下

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

  1. 根目录添加.babelrc文件,配置
{
  "presets": ["env", "stage-0", "react"],
  "plugins": ["transform-runtime"]
}
  1. 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'))

组件中propsstate/data之间的区别

  1. props中的数据是外界传递过来的,state/data中的数据是私有的(异步请求取回来的)
  2. 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>
}
  • 抽离为单独的样式表模块
  1. cnpm i style-loader css-loader -D
  2. webpack.config.js的rules中配置{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
  3. 在根目录下新建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.jsmodulerules配置

 // path - 样式表相对于根目录的路径
 // name - 样式表名称
 // local - 样式类名定义名称
 // hash:length - hash值,默认32位,一般取5~6位可防重复
{ test: /\.css$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]'] }
  • 引用第三方样式的方法
  1. 自己的样式表,用.scss来写
  2. 第三方样式表是.css
  3. 为自己的.scss文件,启动模块化即可
  4. cnpm i sass-loader node-sass -D
  5. webpack.config.jsmodulerules配置
{ 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
    })
  }
}
react数据双向绑定原理

生命周期

  • 组件创建阶段 - 一次
    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包

  1. 为外界传递过来的props属性值,做类型校验
  2. 若外界没有规定属性值,给默认的初始值
    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只能在componentWillReceivePropscomponentWillMountcomponentDidMount中使用

子组件向父组件传值

子组件向父组件传值,用的是方法的调用
父组件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请求

  1. 安装 npm i axios
  2. 引入和挂载
    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'))
  1. 使用发送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中配置transformRequestbaseURL

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

打印结果:

  1. Promise.prototype中的.then(),是为Promise实例,指定【成功】或【失败】之后做的事情
  2. 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)
  })

使用asyncawait简化调用

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可监听

路由配置

  1. npm i react-router-dom -S
  2. App.jsx根组件中引入
import {HashRouter, Route, Link} from 'react-router-dom'
  render() {
    return <HashRouter>
      <div>
        app
      </div>
    </HashRouter>
  }
  1. 使用时,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>&nbsp;&nbsp;
        <Link to="/movie">movie</Link>&nbsp;&nbsp;
        <Link to="/movie/t250">t250</Link>&nbsp;&nbsp;
        <Link to="/about">about</Link>&nbsp;&nbsp;
        <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>&nbsp;&nbsp;
        <Link to="/about/tab2">tab2</Link>&nbsp;&nbsp;
        <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;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容