【第 2 章 React 面向组件编程】

2.1 基本理解和使用

2.1.1 使用React开发者工具调试

![image.png](https://img-blog.csdnimg.cn/img_convert/28492c5c4a6516bab8afabc98be53f82.png#clientId=uaa93a018-086a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=137&id=ufbfe81ef&margin=[object Object]&name=image.png&originHeight=274&originWidth=1168&originalType=binary&ratio=1&rotation=0&showTitle=false&size=37024&status=done&style=none&taskId=u5ac4bd7b-8f55-4662-acab-303ef644488&title=&width=584)

2.1.2 效果

  • 函数式组件
//1.创建函数式组件
function MyComponent() {
  //此处的this为undefined,以为babel编译后开启了严格模式
  console.log(this)
  return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
}
ReactDOM.render(<MyComponent/>,document.querySelector('#test'))

执行了ReactDOM.(…之后,发生了什么)

  1. React解析组件标签,找到了MyComponent组件

  2. 发现组件是用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

![image.png](https://img-blog.csdnimg.cn/img_convert/fceb7f087dfa60378ec87509a69b1919.png#clientId=uaa93a018-086a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=366&id=ua9785460&margin=[object Object]&name=image.png&originHeight=731&originWidth=1595&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55566&status=done&style=none&taskId=uaf173775-a10b-4d08-bd28-b4ca4c7df26&title=&width=797.5)

  • 类式组件
class MyComponent extends React.Component {
  render(){
    console.log(this)
    return <h2>我是用类定义的组件(适用于复杂组件的定义)</h2>
  }
}
ReactDOM.render(<MyComponent/>,document.querySelector('#test'))

执行了ReactDOM.(…之后,发生了什么)

  1. React解析组件标签,找到了MyComponent组件

  2. 发现组件是用类定义的,随后new出来该类的实例,并通过该实例调用到原型的render方法

  3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中

![image.png](https://img-blog.csdnimg.cn/img_convert/cf78e1fb6dfbf0144da84091e2fe4c9e.png#clientId=uaa93a018-086a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=336&id=uf995f461&margin=[object Object]&name=image.png&originHeight=671&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=139883&status=done&style=none&taskId=u65e503f2-ff43-4083-be23-a020bb65de6&title=&width=960)

2.1.3 注意

  • 组件名必须大写

  • 虚拟DOM元素只能有一个根元素

  • 虚拟DOM元素必须有结束标签

2.1.4 渲染类组件标签的基本流程

  • React内部会创建组件实例对象

  • 调用render()得到虚拟DOM,并解析为真实DOM

  • 插入到指定的页面元素内部

2.2 组件三大核心属性1:state

2.2.1 理解

  • state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)

  • 组件被称为状态机,通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.2.2 注意

  • 组件中render方法中的this为组件实例对象

  • 组件自定义方法中this为undefined,如何解决?

    • 强制绑定this,通过函数对象的bind()

    • 箭头函数

  • 状态数据,不能直接修改或更新。要用this.setState({})

2.2.3 代码

//1.创建组件
class Weather extends React.Component {
  //初始化状态
  state = { isHot: true }
  render() {
    console.log(this)
    //读取状态
    const { isHot } = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
  }
  //自定义方法 :赋值语句 + 自定义方法
  changeWeather = () => {
    console.log(this)
    const isHot = this.state.isHot
    this.setState({ isHot: !isHot })
  }
}
ReactDOM.render(<Weather />, document.querySelector('#test'))

![动画.gif](https://img-blog.csdnimg.cn/img_convert/1e082f31255d4214575bbac8cc6bf9c6.gif#clientId=uaa93a018-086a-4&crop=0&crop=0&crop=1&crop=1&from=drop&id=ua20fc4cf&margin=[object Object]&name=动画.gif&originHeight=230&originWidth=628&originalType=binary&ratio=1&rotation=0&showTitle=false&size=74614&status=done&style=none&taskId=u5b8c23be-ee6a-4148-ba3e-a2c2aa64f4f&title=)

2.3 组件三大核心属性2:props

2.3.1 理解

  • 每个组件对象都会有props(properties的简写)属性

  • 组件标签的所有属性都保存在props中

2.3.2 作用

  • 通过标签属性从组件外向组件内传递变化的数据

  • 注意:组件内部不要修改props数据

2.3.3 代码

类式组件

//1.创建组件
class Person extends React.Component {
  //需要this.state,this.props,this.refs的时候才需要构造
  constructor(props){
    console.log(props)
    super(props)
    console.log(this.props)
  }
  //对标签属性进行类型,必要性的限制
  static propTypes = {
    name: PropTypes.string.isRequired,//限制name必传,且为字符串
    sex: PropTypes.string,//限制sex为字符串
    age: PropTypes.number,//限制age为数值
    speak: PropTypes.func//限制speak为函数
  }
  //指定标签默认属性
  static defaultProps = {
    sex: '雌雄莫辨',//sex默认值为'雌雄莫辨'
    age: 20//age默认值为20
  }
  render() {
    console.log(this)
    const { name, age, sex } = this.props
    return (
      <ul>
      <li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
const p = { name: 'rxl', age: 22, sex: 'female' }
ReactDOM.render(<Person name='nino' speak={speak} />, document.querySelector('#test1'))
ReactDOM.render(<Person name='tom' age={23} sex='male' />, document.querySelector('#test2'))
// ReactDOM.render(<Person name='rxl' age='22' sex='female'  />, document.querySelector('#test3')) 
ReactDOM.render(<Person  {...p} />, document.querySelector('#test3'))
function speak() {
  console.log('speakspeakspeak')
}

函数式组件

function Person(props) {
  const { name, age, sex } = props
  return (
    <ul>
    <li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
//对标签属性进行类型,必要性的限制
Person.propTypes = {
  name: PropTypes.string.isRequired,//限制name必传,且为字符串
  sex: PropTypes.string,//限制sex为字符串
  age: PropTypes.number,//限制age为数值
  speak: PropTypes.func//限制speak为函数
}
//指定标签默认属性
Person.defaultProps = {
  sex: '雌雄莫辨',//sex默认值为'雌雄莫辨'
  age: 20//age默认值为20
}
ReactDOM.render(<Person name='nino' />, document.querySelector('#test1'))

2.3.4 效果

![image.png](https://img-blog.csdnimg.cn/img_convert/702f88b90fc5f4a39c63cb0a4482e941.png#clientId=uaa93a018-086a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=411&id=u74858170&margin=[object Object]&name=image.png&originHeight=821&originWidth=1636&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58384&status=done&style=none&taskId=u1612c11a-5865-4d7f-a0ac-7cb40a09f51&title=&width=818)

2.4 组件三大核心属性3:refs与事件处理

2.4.1 理解

组件内的标签可以定义 ref 属性来标识自己

2.4.2 代码

  1. 字符串形式的 ref
<input ref="input1"/>
const {input1} = this.refs
console.log(input1.value)
alert(input1.value)
  1. 回调形式的 ref
<input ref={(c)=>{this.input1 = c}}
const {input1} = this
console.log(input1.value)
alert(input1.value)
  1. createRef 创建 ref 容器·
/* 
本身是一个函数,调用后可返回一个容器,该容器可以存储被ref所标识的节点,该容器是‘专人专用’的
 */
myRef = React.createRef()
<input ref={this.myRef}/>
alert(this.myRef.current.value)

2.4.3 事件处理

  1. 通过 onXxx 属性指定事件处理函数(注意大小写)

    1. React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件(为了更好的兼容性)

    2. React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)(为了更高效)

  2. 通过 event.target 得到发生事件的 DOM 元素对象(不要过度使用)

showData = (e) => {
  console.log(e.target.value)
}

2.5 收集表单数据

2.5.1 理解

包含表单的组件分类

  • 受控组件

    • 将数据通过事件处理保存到state中

    • 再从state中取出来

class Login extends React.Component{
  state={
    username:'',
    password:''
  }
  render(){
    return (
      <form action="http://www.renorchid.xyz" onSubmit={this.handleSubmit}>
      用户名:<input onChange={this.saveUsername} ref={c => this.username=c} type="text" name="username"/><br/>
        密码:<input onChange={this.savePassword} ref={c => this.password=c} type="password" name="password"/><br/>
          <button>登录</button>
      </form>
      )
   }
   handleSubmit = (e) =>{
     e.preventDefault()
     const {username,password} = this.state
     alert(`你输入的用户名是${username},密码是${password}`)
   }
   saveUsername = (e) =>{
     console.log('@',e.target.value)
     this.setState({username:e.target.value})
   }
   savePassword = (e) =>{
     console.log('@',e.target.value)
     this.setState({password:e.target.value})
   }
}
  • 非受控组件

    • 随用随取

2.6 函数柯里化

render() {
  return (
    <form action="http://www.renorchid.xyz" onSubmit={this.handleSubmit}>
    用户名:<input onChange={this.saveFormdata('username')} ref={c => this.username = c} type="text" name="username" />

      密码:<input onChange={this.saveFormdata('password')} ref={c => this.password = c} type="password" name="password" />

        <button>登录</button>
  	</form>
  )
}
saveFormdata = (dataType) => {
  return (e) => { this.setState({ [dataType]: e.target.value }) }
}
  • 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

    • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。

    • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

    • 常见的高阶函数有:**Promise、setTimeout、arr.map()**等等

  • 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

function sum(a){
  return(b)=>{
    return (c)=>{
    return a+b+c
    }
	}
}

2.7 组件的生命周期

2.7.1 理解

  1. 组件从创建到死亡它会经历一些特定的阶段。

  2. React 组件中包含一系列钩子函数(生命周期回调函数), 会在特定的时刻

  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作

2.7.2 生命周期流程图(旧)

![2_react生命周期(旧).png](https://img-blog.csdnimg.cn/img_convert/846a00c7ef00920dd52c9967d3b817ab.png#clientId=u610a2c05-b67f-4&crop=0&crop=0&crop=1&crop=1&from=drop&id=u4b5f47b2&margin=[object Object]&name=2_react生命周期(旧).png&originHeight=670&originWidth=841&originalType=binary&ratio=1&rotation=0&showTitle=false&size=44022&status=done&style=none&taskId=u21debf1c-da18-4906-a169-229afb05dc2&title=)

生命周期的三个阶段(旧)

  1. 初始化阶段: 由 ReactDOM.render()触发—初次渲染

    1. constructor()

    2. componentWillMount()

    3. render()

    4. componentDidMount()

  2. 更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发

    1. shouldComponentUpdate()

    2. componentWillUpdate()

    3. render()

    4. componentDidUpdate()

  3. 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount()
/* 创建组件 */
class Life extends React.Component {
  state = { count: 0 }
  constructor(){
    super()
    console.log('count----constuctor')
  }
  render() {
    console.log('count----render')
    return (
      <div>
      <h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我加一</button>
<button onClick={this.death}>点我销毁</button>
<button onClick={this.forceupdate}>点我强制更新</button>
</div>
)
}
forceupdate = ()=>{
  this.forceUpdate()
}
add = ()=>{
  let {count} = this.state
  console.log(count)
  this.setState({count:count+1})
}
componentWillMount(){
  console.log('count----componentWillMount')
}
componentDidMount(){
  console.log('count----componentDidMount')
}
componentWillUnmount(){
  console.log('count----componentWillUnmount')
}
shouldComponentUpdate(){
  console.log('count----shouldComponentUpdate')
  return false
}
componentWillUpdate(){
  console.log('count----componentWillUpdate')
}
componentDidUpdate(){
  console.log('count----componentDidUpdate')
}
death() {
  ReactDOM.unmountComponentAtNode(document.querySelector('#test'))
}
}
class A extends React.Component{
  state={cpName:'军烨'}
  render(){
    return (
      <div>
      <h2>我最喜欢的cp是{this.state.cpName}</h2>
<button onClick={this.changeName}>点击切换</button>
<B cpName={this.state.cpName} />
  </div>
)
}
changeName = ()=>{
  const {cpName} = this.state
  this.setState({cpName:"周侯"})
}
}
class B extends React.Component {
  render(){
    return (
      <div>{this.props.cpName}</div>
    )
  }
  componentWillReceiveProps(){
    console.log('B--componentWillReceiveProps')
  }
  shouldComponentUpdate(){
    console.log('B----shouldComponentUpdate')
    return true
  }
  componentWillUpdate(){
    console.log('B----componentWillUpdate')
  }
  componentDidUpdate(){
    console.log('B----componentDidUpdate')
  }
}
/* 渲染虚拟DOM到页面 */
ReactDOM.render(<A />, document.querySelector('#test'))

2.7.2 生命周期流程图(新)

![3_react生命周期(新).png](https://img-blog.csdnimg.cn/img_convert/2816da49bb5bcd40471944f99c65d67e.png#clientId=u610a2c05-b67f-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=406&id=u6dc056e1&margin=[object Object]&name=3_react生命周期(新).png&originHeight=788&originWidth=1133&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66531&status=done&style=none&taskId=ue80f2d64-fc7a-47c4-b065-68d08ecf0f2&title=&width=584.4000244140625)

生命周期的三个阶段(新)

  1. 初始化阶段: 由 ReactDOM.render()触发—初次渲染

    1. constructor()

    2. getDerivedStateFromProps

    3. render()

    4. componentDidMount()

  2. 更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发

    1. getDerivedStateFromProps

    2. shouldComponentUpdate()

    3. render()

    4. getSnapshotBeforeUpdate

    5. componentDidUpdate()

  3. 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount()
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .list {
      width: 200px;
      height: 150px;
      background-color: aqua;
      overflow: auto;
    }

    .news {
      height: 30px;
    }
  </style>
</head>

<body>
  <!-- 容器 -->
  <div id="test">

  </div>
  <!-- 引入react核心库 -->
  <!-- 引入react-dom,用于支持react操作dom -->
  <script src="../17.0.1/react.development.js"></script>
  <script src="../17.0.1/react-dom.development.js"></script>
  <!-- 引入Babel,用于将jsx转为js -->
  <script src="../17.0.1/babel.min.js"></script>
  <!-- 一定要写babel -->
  <script type="text/babel">
    class NewsList extends React.Component {
      state = { newsArr: [] }
      render() {
        return (
          <div ref='list' className="list">
            {this.state.newsArr.map((n,index)=>{
              return <div className='news' key={index}>{n}</div>
            })}
          </div>
        )
      }
      componentDidMount() {
        setInterval(() => {
          const { newsArr } = this.state
          const news = '新闻' + (newsArr.length + 1)
          this.setState({ newsArr: [news, ...newsArr] })
        }, 1000);
      }
      getSnapshotBeforeUpdate(){
        /* const list = document.querySelector('.list')
        return list.scrollHeight */
        return this.refs.list.scrollHeight
      }
      componentDidUpdate(prevProps,prevState,snpaValue){
        console.log(prevProps,prevState,snpaValue)
        console.log(this.refs.list.scrollHeight)
        this.refs.list.scrollTop += this.refs.list.scrollHeight - snpaValue
      }
    }
    /* 渲染虚拟DOM到页面 */
    ReactDOM.render(<NewsList />, document.querySelector('#test'))

  </script>
</body>

</html>

2.7.3 重要的钩子

  1. render:初始化渲染或更新渲染调用

  2. componentDidMount:开启监听, 发送 ajax 请求

  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

2.7.4 即将废弃的勾子

  1. componentWillMount

  2. componentWillReceiveProps

  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上 UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

2.8 虚拟 DOM 与 DOM Diffing 算法

2.8.1 基本原理图

![image.png](https://img-blog.csdnimg.cn/img_convert/7997e70399f9c8460d793e7bb9ab7191.png#clientId=ue99b7b7f-841e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=u5f7888a2&margin=[object Object]&name=image.png&originHeight=340&originWidth=780&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104681&status=done&style=none&taskId=u1facd62d-5dbd-434d-8d6c-ad084461660&title=&width=583)


<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>key的作用</title>
</head>
<body>
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel -->
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel">
	/*
   经典面试题:
      1). react/vue中的key有什么作用?(key的内部原理是什么?)
      2). 为什么遍历列表时,key最好不要用index?
      
			1. 虚拟DOM中key的作用:
					1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

					2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 
												随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

									a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
												(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
												(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

									b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
												根据数据创建新的真实DOM,随后渲染到到页面
									
			2. 用index作为key可能会引发的问题:
								1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
												会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

								2. 如果结构中还包含输入类的DOM:
												会产生错误DOM更新 ==> 界面有问题。
												
								3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
									仅用于渲染列表用于展示,使用index作为key是没有问题的。
					
			3. 开发中如何选择key?:
								1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
								2.如果确定只是简单的展示数据,用index也是可以的。
   */
	
	/* 
		慢动作回放----使用index索引值作为key

			初始数据:
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			初始的虚拟DOM:
					<li key=0>小张---18<input type="text"/></li>
					<li key=1>小李---19<input type="text"/></li>

			更新后的数据:
					{id:3,name:'小王',age:20},
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			更新数据后的虚拟DOM:
					<li key=0>小王---20<input type="text"/></li>
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>

	-----------------------------------------------------------------

	慢动作回放----使用id唯一标识作为key

			初始数据:
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			初始的虚拟DOM:
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>

			更新后的数据:
					{id:3,name:'小王',age:20},
					{id:1,name:'小张',age:18},
					{id:2,name:'小李',age:19},
			更新数据后的虚拟DOM:
					<li key=3>小王---20<input type="text"/></li>
					<li key=1>小张---18<input type="text"/></li>
					<li key=2>小李---19<input type="text"/></li>


	 */
	class Person extends React.Component{

		state = {
			persons:[
				{id:1,name:'小张',age:18},
				{id:2,name:'小李',age:19},
			]
		}

		add = ()=>{
			const {persons} = this.state
			const p = {id:persons.length+1,name:'小王',age:20}
			this.setState({persons:[p,...persons]})
		}

		render(){
			return (
				<div>
					<h2>展示人员信息</h2>
					<button onClick={this.add}>添加一个小王</button>
					<h3>使用index(索引值)作为key</h3>
					<ul>
						{
							this.state.persons.map((personObj,index)=>{
								return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
							})
						}
					</ul>
					<hr/>
					<hr/>
					<h3>使用id(数据的唯一标识)作为key</h3>
					<ul>
						{
							this.state.persons.map((personObj)=>{
								return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
							})
						}
					</ul>
				</div>
			)
		}
	}

	ReactDOM.render(<Person/>,document.getElementById('test'))
</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呐呐呐呐。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值