定义组件的2种方式
工厂函数组件
DEMO:
<div id="test"></div>
<script type="text/babel">
function MyComponent() {
return <h2>工厂函数组件(无状态/简单组件)</h2>
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
ES6类的方式定义组件
DEMO:
<div id="test"></div>
class MyComponent2 extends React.Component{
//重写父类的render方法
render(){
return <h2>ES6类组件(有状态/复杂组件)</h2>
}
}
ReactDOM.render(<MyComponent2/>,document.getElementById('test'))
注意
1) 组件名必须首字母大写
2) 虚拟DOM元素只能有一个根元素
3) 虚拟DOM元素必须有结束标签
组件三大属性
state、props、refs
state
1) state是组件对象最重要的属性, 值是对象(可以包含多个数据)
2) 组件被称为”状态机”, 通过更新组件的state来更新对应的页面显示(重新渲染组件)
DEMO(点击文字变化内容):
效果:
代码:
<div id="demo"></div>
<script type="text/babel">
class Weather extends React.Component{
constructor(props) {
super(props);
this.state = {
isHot:true
}
this.clickHandle = this.clickHandle.bind(this)
}
clickHandle(){
let {isHot} = this.state
isHot = !isHot
this.setState({
isHot
})
}
render(){
return <h1 onClick={this.clickHandle}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1>
}
}
ReactDOM.render(<Weather/>,document.getElementById('demo'))
</script>
以上代码的简写方式:
class Weather extends React.Component {
state = {
isHot: true
}
handleClick = () => {
let {isHot} = this.state
isHot = !isHot
this.setState({isHot})
}
render() {
return <h1 onClick={this.handleClick}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
}
ReactDOM.render(<Weather/>, document.getElementById('demo'))
- 强烈注意:
1) 组件内置的方法中的this为组件对象
2) 在组件类中自定义的方法中this为undefined
a. 强制绑定this: 通过函数对象的bind()
b. 箭头函数(ES6模块化编码时才能使用)
3) 状态数据,不能直接修改或更新
props
1) 每个组件对象都会有props(properties的简写)属性
2) 组件标签的所有属性都保存在props中
- 作用
1) 通过标签属性从组件外向组件内传递变化的数据
2) 注意: 组件内部不要修改props数据
效果:
代码:
<div id="demo1"></div>
<div id="demo2"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
let {name,sex,age} = this.props
return (
<div>
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
<hr/>
</div>
)
}
}
let p1 = {
name:'周杰伦',
sex:'男',
age:34,
}
let p2 = {
name:'蔡依林',
sex:'女',
age:33,
}
ReactDOM.render(<Person name={p1.name} sex={p1.sex} age={p1.age}/>, document.getElementById('demo1'))
ReactDOM.render(<Person {...p2}/>, document.getElementById('demo2'))
</script>
在以上代码基础上增加参数类型限制并设置默认值
<!--引入限制参数类型及必要性的库-->
<script src="./js/prop-types.js"></script>
class Person extends React.Component {
//限制传参的类型
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string.isRequired,
age: PropTypes.number
}
//设置默认值
static defaultProps = {
age: 18
}
render() {
let {name, sex, age} = this.props
return (
<div>
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
<hr/>
</div>
)
}
}
let p1 = {
name: '周杰伦',
sex: '男',
age: '34',
}
let p2 = {
name: '蔡依林',
sex: '女',
}
ReactDOM.render(<Person name={p1.name} sex={p1.sex} age={p1.age}/>, document.getElementById('demo1'))
ReactDOM.render(<Person {...p2}/>, document.getElementById('demo2'))
- 组件的props和state属性的区别
1) state: 组件自身内部可变化的数据
2) props: 从组件外部向组件内部传递数据, 组件内部只读不修改
refs
需求: 自定义组件, 功能说明如下:
1. 点击按钮, 提示第一个输入框中的值
2. 当第2个输入框失去焦点时, 提示这个输入框中的值
DEMO效果:
老语法(常用):
<div id="demo"></div>
<script type="text/babel">
class UserInput extends React.Component {
clickHandle = () => {
console.log(this.refs)
let {input1} = this.refs
alert(input1.value)
}
blurHandle = (event) => {
alert(event.target.value)
}
render() {
return (
<div>
<input type="text" ref="input1"/>
<button type="button" onClick={this.clickHandle}>点击提示</button>
<input type="text" onBlur={this.blurHandle}/>
</div>
)
}
}
ReactDOM.render(<UserInput/>, document.getElementById('demo'))
</script>
新语法1(回调ref方式,不推荐使用):
class UserInput extends React.Component {
clickHandle = () => {
console.log(this)
let {input1} = this
alert(input1.value)
}
blurHandle = (event) => {
alert(event.target.value)
}
render() {
return (
<div>
<input type="text" ref={(input) => {this.input1 = input}}/>
<button type="button" onClick={this.clickHandle}>点击提示</button>
<input type="text" onBlur={this.blurHandle}/>
</div>
)
}
}
ReactDOM.render(<UserInput/>, document.getElementById('demo'))
新语法2(createRef方式):
class UserInput extends React.Component {
input1 = React.createRef() //创建一个装ref的容器(对象),只能保存一个ref
input2 = React.createRef() //创建一个装ref的容器(对象),只能保存一个ref
clickHandle = () => {
console.log(this)
alert(this.input1.current.value)
}
blurHandle = () => {
alert(this.input2.current.value)
}
render() {
return (
<div>
<input type="text" ref={this.input1}/>
<button type="button" onClick={this.clickHandle}>点击提示</button>
<input type="text" ref={this.input2} onBlur={this.blurHandle}/>
</div>
)
}
}
ReactDOM.render(<UserInput/>, document.getElementById('demo'))
综合练习 todo list
实现效果:
代码:
<div id="demo"></div>
<script type="text/babel">
class App extends React.Component {
state = {
todos: ['吃饭', '睡觉', '打豆豆'],
}
//状态在哪里,更新状态的方法就在哪里
addTodos = (data) => {
//1.获取原数据,复制原数组为一个新数组
let todos = [...this.state.todos]
//2.把数据追加进去
todos.unshift(data)
//3.更新状态
this.setState({todos})
}
render() {
let {todos} = this.state
return (
<div>
<h1>Simple Todo List</h1>
<Add count={todos.length} addTodos={this.addTodos}/>
<List todos={todos}/>
</div>
)
}
}
class Add extends React.Component {
addItem = () => {
let {addTodos} = this.props
//1.获取用户输入
let value = this.item.value.trim()
//2.将用户的输入追加到todos数组
addTodos(value)
//3.清空输入框
this.item.value = ''
}
render() {
let {count} = this.props
return (
<div>
<input type="text" ref={(input) => {
this.item = input
}}/>
<button onClick={this.addItem}>Add #{count}</button>
</div>
)
}
}
class List extends React.Component {
render() {
let {todos} = this.props
return (
<ul>
{todos.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
)
}
}
ReactDOM.render(<App/>, document.getElementById('demo'))
</script>
受控组件与非受控组件
受控组件
在HTML中,标签<input>
、<textarea>
、<select>
的值的改变通常是根据用户输入进行更新。在React中,可变状态通常保存在组件的状态属性中,并且只能使用setState()
更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。
非受控组件
表单数据由DOM本身处理。即不受setState()
的控制,与传统的HTML表单输入相似,input输入值即显示最新值(使用 ref从DOM获取表单值)
DEMO需求:
自定义包含表单的组件
1. 界面如图所示
2. 输入用户名密码后, 点击登陆提示输入信息
3. 密码是点击时候直接获取的,用户名是当用户输入时存入状态
4. 不提交表单
DEMO实现效果:
代码:
class Login extends React.Component {
state = {
username: ''
}
clickHandle = (event) => {
event.preventDefault()
let {username} = this.state
let {password} = this
alert(`用户名:${username},密码:${password.value}`)
}
changeHandle = (event) => {
let username = event.target.value
this.setState({username})
}
render() {
return (
<form action="" method="post">
用户名:<input type="text" onChange={this.changeHandle}/>
密码:<input type="password" ref={(input) => {this.password = input}}/>
<button onClick={this.clickHandle}>提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('demo'))
生命周期
生命周期图解
- DEMO需求:
- 让指定的文本做显示/隐藏的渐变动画
- 切换持续时间为2S
- 点击按钮从界面中移除组件界面
- DEMO效果:
-
DEMO代码
<div id="demo"></div>
<script type="text/babel">
class Life extends React.Component {
state = {
'opacity': 1
}
//组件被挂载时执行
componentDidMount() {
let {opacity} = this.state
this.timeId = setInterval(() => {
console.log('定时器执行')
opacity -= 0.1
if (opacity < 0) {
opacity = 1
}
this.setState({
opacity
})
}, 200)
}
//组件将被卸载时执行
componentWillUnmount() {
clearInterval(this.timeId)
}
removeComponent() {
ReactDOM.unmountComponentAtNode(document.getElementById('demo'))
}
render() {
let {opacity} = this.state
return (
<div>
<h2 style={{opacity}}>文本逐渐消失</h2>
<button onClick={this.removeComponent}>点我隐藏</button>
</div>
)
}
}
ReactDOM.render(<Life/>, document.getElementById('demo'))
</script>
初始化
触发条件:ReactDOM.render(<MyComponent/>)
执行顺序:
constructor()
componentWillMount() //将被挂载
render() //提供虚拟DOM,可能会调用多次(1+n)。
componentDidMount() //启动定时器、发送Ajax请求、只执行一次。
更新
触发条件:this.setState({})
执行顺序:
componentWillUpdate()
render()
componentDidUpdate()
卸载
触发条件:ReactDOM.unmountComponentAtNode()
执行顺序:
componentWillUnmount() //收尾工作,例如:清除定时器,只执行一次。
虚拟DOM与DOM Diff算法
- 基本原理图