文章目录
面向组件编程
- 函数式组件 —— 适用于简单组件
- 类式组件 —— 适用于复杂组件
函数式组件
流程:
- 创建函数式组件
// 主要注意函数要大写,要有返回值
function MyComponent() {
return <h2>我是用函数定义的组件</h2>
}
- 将渲染组件到页面
// 注意函数式组件需要用标签表示
ReactDOM.render(<MyComponent/>, document.getElementById('test'));
- React解析组件表情啊,找到组件
- 发现组件是用函数定义的,随后调用函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
类式组件
- 创建类式组件
// 需要继承React.Component
// 必须调用render函数
class MyComponent extends React.Component{
render() {
return <h2>我是用类定义的组件</h2>
}
}
- 将渲染组件到页面
// 注意函数式组件需要用标签表示
ReactDOM.render(<MyComponent/>, document.getElementById('test'));
- React解析组件表情啊,找到组件
- 发现组件是用类定义的,随后用new出来该类的实例,并通过该实例调用到原型上的render方法
- 将该render返回的虚拟DOM转为真实DOM,随后呈现在页面上
组件实例的三个核心属性之一:State
对State理解:
人-状态-影响-行为
组件-状态-驱动-页面
可能出现的类中this问题
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {isHot: false};
}
render() {
const {isHot} = this.state;
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>;
}
changeWeather() {
console.log(this);
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'));
-
changeWeather
放在哪里 —— Weather的原型对象上,供实例使用 -
由于
changeWeather
是作为onClick
的回调,所以不是实例调用的,是直接调用 -
类中的方法默认开启局部的严格模式,所以
changeWeather
中的this为undefined
解决方法:
constructor(props) {
super(props);
this.state = {isHot: false};
this.changeWeather = this.changeWeather.bind(this)
}
- 通过bind方法修改this的指向对象并且创建新方法
- 右边的
this.changeWeather
指的是类中的changeWeather
方法,而左边的this.changeWeather
是指向新方法的方法名
setState方法的使用
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {isHot: false};
this.changeWeather = this.changeWeather.bind(this)
}
render() {
const {isHot} = this.state;
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>;
}
changeWeather() {
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
}
精简化
class Weather extends React.Component {
// 初始化方法
state = {isHot: false, wind: '微风'};
render() {
const {isHot, wind} = this.state;
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}, {wind}</h1>;
}
// 自定义方法 -- 用赋值语句的形式+箭头函数
changeWeather = () => {
const isHot = this.state.isHot;
this.setState({isHot: !isHot});
}
}
总结
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
- 组件的
render
方法中的this为组件实例对象 - 组件自定义的方法中this为
undefined
- 强制绑定this:通过函数对象的
bind()
- 箭头函数
- 强制绑定this:通过函数对象的
- 状态数据,不能直接修改或更新,需要使用
setState()
进行修改和更新
组件实例的三个核心属性之一:Props
简单使用
class Person extends React.Component {
render() {
console.log(this);
const {name, age, sex} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Tom" age="123" sex="male"/>, document.getElementById('test'))
Prop限制
需要引入JS文件/prop-types.js
对Prop属性进行类型限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func
};
对Prop属性进行默认设置
Person.defaultProps = {
sex: '男',
age: 18
};
精简化
class Person extends React.Component {
render() {
console.log(this);
const {name, age, sex} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func
};
static defaultProps = {
sex: '男',
age: 18
};
}
小问题
构造器是否接受props,是否传递给super,取决于:是否希望在构造器中通过this
访问props
但是这个问题几乎不会遇到,因为很少使用this.props
constructor(props) {
super(props);
console.log(this.props);
console.log(props);
}
组件实例的三个核心属性之一:Ref与事件处理
字符串形式Ref
字符串形式Ref已经弃用,所以不推荐使用,使用过多会造成效率上的问题
class Demo extends React.Component {
showData = () => {
const {input1} = this.refs;
alert(input1.value);
};
showData2 = () => {
const {input2} = this.refs;
alert(input2.value);
};
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button ref="button" onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
回调函数形式Ref
关于回调refs说明:如果是内联函数的方法定义,在更新过程中会被执行两次,第一次传入参数null,第二次会传入参数DOM元素,这是因为在每次渲染时会创建一个新的函数实例,所以React会清空旧的ref并且设置新的。但是大多数情况下时无关紧要的。
回调函数形式就是通过回调函数将真实DOM返回到this的属性中
class Demo extends React.Component {
showData = () => {
const {input1} = this;
alert(input1.value);
};
showData2 = () => {
const {input2} = this;
alert(input2.value);
};
render() {
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
CreateRef函数形式Ref
class Demo extends React.Component {
// this.myRef = React.createRef();
// this.myRef2 = React.createRef();
showData = () => {
alert(this.myRef.current.value);
};
showData2 = () => {
alert(this.myRef2.current.value);
};
render() {
return (
<div>
<input ref={this.myRef = React.createRef()} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={this.myRef2 = React.createRef()} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
事件处理
- 通过onXxx属性指定事件处理函数(注意大小写),React二次封装事件,为了更好的兼容性,更高效
- React使用的是自定义合成事件,而不是使用的原生DOM事件
- React中的事件是通过事件委托方式处理的,委托给组件最外层的元素
- 通过
event.target
得到发生事件的DOM元素对象 —— 减少使用ref,当事件处理的属性是跟标签相关的,则不需要ref指向,只需要使用event.target
showData2 = (event) => {
alert(event.target.value);
};
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
包含表单的组件分类
非受控组件
现用想取
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault();
const {username, password} = this;
alert(`username: ${username}, password: ${password}`);
};
render() {
return (
<form action="https://www.baidu.comn" onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('test'));
受控组件
简单理解就是VUE中的v-model
双向绑定,就是随着输入维护状态
class Login extends React.Component {
state = {
username: '',
password: '',
};
handleSubmit = (event) => {
const {username, password} = this.state;
alert(`username: ${username}, password: ${password}`);
};
saveUsername = (event) => {
this.setState({
username: event.target.value,
})
};
savePassword = (event) => {
this.setState({
password: event.target.value,
})
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
高级函数和柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那就是高阶函数
- 若A函数,接受的参数是一个函数
- 罗A函数,调用的返回值依然是一个函数
柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理函数编码形式
对于在受控组件中的例子,可以使用高阶函数和柯里化技术进行精简化处理
class Login extends React.Component {
state = {
username: '',
password: '',
};
handleSubmit = (event) => {
const {username, password} = this.state;
alert(`username: ${username}, password: ${password}`);
};
saveFormData = (dataType) => {
return (event) => {
this.setState({[dataType]: event.target.value});
}
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
如果不适用柯里化技术,还可以如下写法
saveFormDataSecond = (dateType, event) => {
this.setState([dateType], event.target.value)
};
用户名:<input onChange={event => this.saveFormDataSecond('username', event)} type="text" name="username"/
生命周期
引出生命周期的例子
若要在render()
中使用计时器,会因为render()
函数的性质而生成指数级个计时器,占用大量CPU
// 初始化会调用一次,后续每次更新状态都会重新调用一次,共1+n次
// 因此计时器会指数级开启
render() {
setInterval(() => {
let {opacity} = this.state;
opacity -= 0.1
if (opacity <= 0) opacity = 1;
// 设置新的透明度
this.setState({opacity})
}, 200);
return (
<div>
<h2 style={{opacity: this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
为了在组件挂载前调用后只调用一次函数,因此使用到生命周期中的componentDidMount()
函数
// 组件挂载完毕之后调用
componentDidMount() {
setInterval(() => {
let {opacity} = this.state;
opacity -= 0.1;
if (opacity <= 0) opacity = 1;
// 设置新的透明度
this.setState({opacity})
}, 200);
}
但是在点击button调用death()
函数后,虽然会清除计时器和卸载组件,但是调试器会出现错误,这是因为此时状态发生了变化,就会调用render()
函数,但是render()
函数中的组件已经卸载了,所以同样使用到生命周期中的componentWillUnmount()
函数
// 组件将要卸载
componentWillUnmount() {
// 清楚计时器
clearInterval(this.timer);
}
生命周期图(旧)
暂时无图
挂载时的生命周期(旧)
class Count extends React.Component {
// 构造器
constructor(props) {
console.log('count-constructor');
super(props);
this.state = {count: 0};
}
// 组件将要挂载的钩子
componentWillMount() {
console.log('count-componentWillMount');
}
render() {
console.log('count-render');
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.addCount}>点我+1</button>
</div>
)
}
// 组件完成挂载的钩子
componentDidMount() {
console.log('count-componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount() {
console.log('count-componentWillUnmount');
}
// 加1按钮的回调函数
addCount = () => {
const {count} = this.state;
// 更新状态
this.setState({
count: count + 1
})
};
}
组件更新的生命周期(旧)
更新阶段由组件内部this.setState
或者父组件重新render
触发
// 控制组件更新的阀门
// 需要有返回值且返回值为true或者false,如果不写系统也会有该函数,且默认返回true
shouldComponentUpdate() {
console.log('count-shouldComponentUpdate');
return true;
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('count-componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('count-componentDidUpdate');
}
shouldComponentUpdate()
函数需要有返回值且返回值为true或者false,如果不写系统也会有该函数,且默认返回true- 在执行
forceUpdate()
函数的过程中,不会调用shouldComponentUpdate()
函数。所谓强制更新,就是不更改任何状态中的数据。
生命周期图(新)
暂时无图,后续更新
比较旧生命周期图:
废弃:
componentWillMount
componentWillReceiveProps
componentWillUpdate
添加:
-
getDerivedStateFromProps
:新的静态
getDerivedStateFromProps
生命周期方法在组件实例化之后以及重新渲染之前调用。它可以返回一个对象来更新state
,或者返回null
来表示新的props
不需要任何state
的更新。与
componentDidUpdate
一起,这个新的生命周期涵盖过时的componentWillReceiveProps
的所有用例。 -
getSnapshotBeforeUpdate
:新的
getSnapshotBeforeUpdate
生命周期方法在更新之前(如:更新 DOM 之前)被调用。此生命周期的返回值将作为第三个参数传递给componentDidUpdate
。(通常不需要,但在重新渲染过程中手动保留滚动位置等情况下非常有用。)与
componentDidUpdate
一起,这个新的生命周期涵盖过时的componentWillUpdate
的所有用例。
Key作用
经典面试题:
- react/vue中的key作用,内部原理是什么
- 为什么遍历列表时,key最好不要用index
- 虚拟DOM中key的作用
- 简单:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
- 复杂:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后react进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则:
- 旧虚拟DOM中找到了与新虚拟DOM相同key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同key:根据数据创建新的真实DOM,随后渲染到页面中
- 旧虚拟DOM中找到了与新虚拟DOM相同key:
- 用index作为key可能引发的问题
- 若对数据进行:逆序添加,逆序删除等破坏顺序操作 —— 会产生没必要的真实DOM更新,虽然界面没有问题,但是效率低
- 如果结构中还包含输入类的DOM:会产生错误的DOM更新 —— 界面出现问题
- 如果不存在对数据的逆序添加等破坏顺序操作,仅用于渲染列表用于展示的话是没有问题的
- 开发中如何选择key
- 最好使用每条数据的唯一标识作为key
- 如果确定只是简单的展示数据,用index也是可以的