第二章 React面向组件编程
基本理解和使用
自定义组件
- 定义组件
- 工厂函数组件(
简单组件
)
function MyComponent () {
return <h2>工厂函数组件(简单组件)</h2>
}
没有状态的组件
- ES6类组件(
复杂组件
)
class MyComponent2 extends React.Component {
render () {
console.log(this) // MyComponent2的实例对象
return <h2>ES6类组件(复杂组件)</h2>
}
}
- 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
注意事项
组件名必须首字母大写
虚拟DOM元素只能有一个根元素
虚拟DOM元素必须有结束标签
组件三大属性(state)
- state是组件对象最重要的属性, 值是对象(可以包含多个数据)
- 组件被称为"
状态机
", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
操作
- 初始化状态
constructor (props) {
super(props)
// 初始化状态
this.state = {
isLikeMe: true
}
- 读取某个状态->绑定this为组件对象
this.change = this.change.bind(this)
- 更新状态->组件界面更新
this.setState({
isLikeMe: !this.state.isLikeMe
})
组件三大属性(props)
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
操作
- 内部读取某个属性值
this.props.propertyName
- 对props中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number
}
- 扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>
...
的作用:
打包
function fn(...as) {} fn( 1,2,3)
解包
const arr1 =[1,2,3] const arr2 = [6, ...arr1, 9]
- 默认属性值
Person.defaultProps = {
sex: '男',
age: 18
}
- 组件类的构造函数
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
props和state属性区别
props | state |
用于定义外部接口 | 用于记录内部状态 |
赋值在于外部世界使用组件 | 赋值在于组件内部 |
不改变组件值 | 让组件来修改的 |
不能在constructor中设置默认值 | 只能在constructor中设置默认值 |
从组件外部向组件内部传递数据, 组件内部只读不修改 | 组件自身内部可变化的数据 |
none | setState修改值为异步的 |
组件三大属性(refs与事件处理)
//定义组件
class MyComponent extends React.Component {
constructor(props) {
super(props) // 调用父类(Component)的构造函数
//console.log(this)
// 将自定义的函数强制绑定为组件对象
this.showInput = this.showInput.bind(this) // 将返回函数中的this强制绑定为指定的对象, 并没有改变原来的函数中的this
}
// 自定义的方法中的this默认为null
showInput() {
// alert(this) //this默认是null, 而不是组件对象
// 得到绑定在当前组件对象上的input的值
alert(this.msgInput.value)
}
handleBlur(event) {
alert(event.target.value)
}
render() {
return (
<div>
<input type="text" ref={input => this.msgInput = input}/>{' '}
<button onClick={this.showInput}>提示输入数据</button>
{' '}
<input type="text" placeholder="失去焦点提示数据" onBlur={this.handleBlur}/>
</div>
)
}
}
// 渲染组件标签
ReactDOM.render(<MyComponent/>, document.getElementById('example'))
refs属性
- 组件内的标签都可以定义ref属性来标识自己
<input type="text" ref={input => this.msgInput = input}/>
- 回调函数在组件初始化渲染完或卸载时自动调用
- 在组件中可以通过this.msgInput来得到对应的真实DOM元素
- 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据
事件处理
- 通过onXxx属性指定组件的事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
- 通过event.target得到发生事件的DOM元素对象
<input onFocus={this.handleClick}/>
handleFocus(event) {
event.target //返回input对象
}
注意
- 组件内置的方法中的this为组件对象
- 在组件类中自定义的方法中this为null
- 强制绑定this: 通过函数对象的bind()
- 箭头函数(ES6模块化编码时才能使用)
组件的组合
流程
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果(只有静态界面,没有动态数据和交互)
- 实现动态组件
- 实现动态显示初始化数据
- 交互功能(从绑定事件监听开始)
问题1:数据保存在哪个组件内?
看数据是某个组件需要(给它),还是某些组件需要(给共同的父组件)
问题2:需要在子组件中改变父组件的状态
子组件中不能直接改变父组件的状态
状态在哪个组件,更新状态的行为就应该定义在哪个组件
解决:父组件定义函数,传递给子组件,子组件调用
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
todos: ['吃饭', '睡觉', '打豆豆']
}
this.addTodo = this.addTodo.bind(this)
}
addTodo(todo) {
const {todos} = this.state
todos.unshift(todo)
//更新状态
this.setState({todos})
}
render() {
const {todos} = this.state
return (
<div>
<h1>Simple TODO List</h1>
<Add count={todos.length} addTodo={this.addTodo}/>
<List todos={todos}/>
</div>
)
}
}
class Add extends React.Component {
constructor(props) {
super(props);
this.add = this.add.bind(this)
}
add() {
// 读取输入数据
const todo = this.todoinput.value.trim()
// 检查合法性
if (!todo) {
return
}
// 保存到todos
this.props.addTodo(todo)
// 清除输入
this.todoinput.value = ''
}
render() {
return (
<div>
<input type="text" ref={input => this.todoinput = input}/>
<button onClick={this.add}> add #{this.props.count + 1}</button>
</div>
)
}
}
Add.propTypes = {
count: PropTypes.number.isRequired,
addTodo:PropTypes.func.isRequired
}
class List extends React.Component {
render() {
const {todos} = this.props
return (
<ul>
{
todos.map((todo, index) => {
return <li key={index}>{todo}</li>
})
}
</ul>
)
}
}
List.propTypes = {
todos: PropTypes.array.isRequired,
}
// 渲染应用组件标签
ReactDOM.render(<App/>, document.getElementById('example'))
</script>
收集表单数据
- 包含表单的组件分类
- 受控组件: 表单项输入数据能自动收集成状态
- 非受控组件: 需要时才手动读取表单输入框中的数据
效果
<script type="text/babel">
class LoginForm extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
username: ''
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({username: event.target.value})
}
handleSubmit(event) {
alert(`准备提交的用户名为: ${this.state.username}, 密码:${this.pwdInput.value}`)
// 阻止事件的默认行为: 提交表单
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit} action="/test">
<label>
用户名:
<input type="text" value={this.state.username} onChange={this.handleChange}/>
</label>
<label>
密码:
<input type="password" ref={(input) => this.pwdInput = input}/>
</label>
<input type="submit" value="登陆"/>
</form>
)
}
}
ReactDOM.render(<LoginForm/>, document.getElementById('example'))
</script>
组件生命周期
效果
- 组件对象从创建到死亡它会经历特定的生命周期阶段
- React组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调
<script type="text/babel">
class Fade extends React.Component {
constructor (props) {
super(props)
console.log('constructor(): 创建组件对象')
this.state = {
opacity: 1
}
this.removeComponent = this.removeComponent.bind(this)
}
componentWillMount () {
console.log('componentWillMount(): 初始化将要挂载')
}
componentDidMount () {// 在此方法中启动定时器/绑定监听/发送ajax请求
console.log('componentDidMount(): 初始化已经挂载')
// 保存到当前组件对象中
this.intervalId = setInterval(function () {
// 得到当前opacity
let {opacity} = this.state
// 更新opacity
opacity -= 0.1
if(opacity<=0) {
opacity = 1
}
// 更新状态
this.setState({opacity})
}.bind(this), 200)
}
componentWillUpdate () {
console.log('componentWillUpdate(): 将要更新')
}
componentDidUpdate () {
console.log('componentDidUpdate(): 已经更新')
}
componentWillUnmount () {
// 清除定时器/解除监听
console.log('componentWillUnmount(): 将要被移除')
clearInterval(this.intervalId)
}
removeComponent () {
ReactDOM.unmountComponentAtNode(document.getElementById('example'))
}
render() {
console.log('render() 渲染组件')
return (
<div>
<h2 style={{opacity:this.state.opacity}}>{this.props.content}</h2>
<button onClick={this.removeComponent}>不活了</button>
</div>
)
}
}
ReactDOM.render(<Fade content="react学不会, 怎么办?"/>, document.getElementById('example'))
</script>
生命周期流程图
- 组件对象从创建到死亡它会经历特定的生命周期阶段
- React 组件对象包含一系列的钩子函数(生命周期回调函数),在生命周期特定时刻回调
- 我们在定义组件时,可以重写特定的生命周期回调函数,做特定的工作
生命周期详述
- Mount:挂载过程,第一次将组件插入到真实 DOM
- Update:更新过程,组件被重新渲染
- Unmount:卸载过程,被移出真实 DOM
生命周期流程
1)创建阶段(第一次初始化渲染显示)
ReactDOM.render()
- constructor():
super(props)
指定 this,this.state={}
创建初始化状态(getDefaultProps、getInitialState) - componentWillMount()
:组件将要挂载到页面上
- 可以在这里调用 setState() 方法修改 state
- render():创建虚拟 DOM 但是还没有挂载上去
- componentDidMount()
:已经挂载到页面上(初始界面已经渲染完毕)
- 可以在这里通过 this.getDOMNode() 来进行访问 DOM 结构
- 可以在这里发送 ajax 请求
- 添加监听器/订阅
2)运行阶段(二次渲染)
父组件传递的
props
发生更新,就会调用 componentWillReceiveProps()
- componentWillReceiveProps(nextProps):当子组件接受到 nextProps 时,不管这个 props 与原来的是否相同都会调用
props
改变或者调用 this.setState() 方法更新state
,都会触发组件的更新,调用后面的钩子函数
- shouldComponentUpdata(nextProps, nextState):接收一个新的 props 和 state,返回true/false,表示是否允许更新
- 通常情况下为了优化,需要对新的 props 以及 state 和原来的数据作对比,如果发生变化才更新
调用 this.forceUpdate() 方法会直接进入 componentWillUpdate。跳过 shouldComponentUpdate()
- componentWillUpdate():将要更新
- render():重新渲染
- componentDidUpdate():已经完成更新
除了首次 render 之后调用 componentDidMount
,其它 render 结束之后都是调用 componentDidUpdate
3)销毁阶段(移除组件)
执行 ReactDOM.unmountComponentAtNode(containerDom) 用来使组件从真实 DOM 中卸载(开始销毁阶段)
- componentWillUnmount():组件将要被移除时(移出前)回调
- 一般在
componentDidMount
里面注册的事件需要在这里删除
重要勾子
- render():初始化渲染时或更新渲染时调用
- componentDidMount():开启监听,可以初始化一些异步操作:启动定时器/发送 ajax 请求
- componentWillUnmount():做一些收尾工作,如:清理定时器
- componentWillReceiveProps():当组件接收到(父元素传递的)新的 props 属性前调用
虚拟DOM与DOM Diff算法
效果
<script type="text/babel">
class HelloWorld extends React.Component {
constructor(props) {
super(props)
this.state = {
date: new Date()
}
}
componentDidMount () {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
render () {
console.log('render()')
return (
<p>
Hello, <input type="text" placeholder="Your name here"/>!
<span>It is {this.state.date.toTimeString()}</span>
</p>
)
}
}
ReactDOM.render(
<HelloWorld/>,
document.getElementById('example')
)
</script>
基本原理图