React

React

语法基础

jsx语法规范

  • 定义虚拟DOM时,不要写引号。
  • 标签中混入JS表达式要用{}.
  • 样式的类名指定不要用class而要用className
  • 内联样式。要用style={{key:value}}的形式去写。
  • 只有一个根标签。
  • 标签必须闭合。
  • 标签首字母
    • 若小写字母开头,则将改标签为html中同名的元素。
    • 若大写字母开头,React将该标签当做是组件。

列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
const data = ['Angular','React','Vue'];
const VDOM = (
<div>
<ul>
{
data.map((item,index) =>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM,document.getElementById('root'));

组件的定义方式

函数式组件

1
2
3
4
function MyComponent(){
return <h2>我是函数定义的组件</h2>
}
ReactDOM.render(<MyComponent/>,document.getElementById('root'));

执行ReactDOM.render(<MyComponent/>,document.getElementById('root'))后发生了什么?

  • React解析组件标签,找到MyComponent组件。
  • 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面上。

类式组件

1
2
3
4
5
6
class MyComponent extends React.Component {
render(){
return <h2>我的用类定义的组件</h2>
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('root'));

执行ReactDOM.render(<MyComponent/>,document.getElementById('root'))后发生了什么?

  • React解析组件标签,找到MyComponent组件。
  • 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
  • render返回的虚拟DOM转为真实DOM,随后呈现在页面上。

类的知识点补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person{
constructor(name,age){
this.name = name;
this.age = age;
//this.speak = this.speak.bind(this);
}
speak(){
console.log(this);
}
}
const p1 = new Person();
p1.speak(); //p1
const x = p1.speak;
x(); //undefined
  • 类中的方法是开启局部严格模式的。

  • 通过类的实例调用原型链上的方法时,this指向的是类的实例。

  • 直接调用类的原型链上的方法时,this指向的是undefined,正常情况下应该是window,但是类中的方法是开启局部严格模式的,因此指向的是undefined。

  • 如何解决让类中的方法的this始终指向类的实例对象?在构造器中添加如下代码

    this.speak = this.speak.bind(this)

    直接将原型上的方法加到类的实例对象上去。

组件属性

属性的三大属性是针对于类式组件。(除了props)

state

  • 状态不可以直接更改,要借助内置的APIsetState(在原型链的React.Component身上)进行更改。
  • setState的过程是一种合并,而不是替换。

标准写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Weather extends React.Component{
constructor(props){
super(props);
this.state = {isHot:false,wind:'slight'};
//如果没有这句,那么在render()执行的时候changeWeather的this是null
//因为在render()中changeWeather()的调用是直接调用而不是实例调用
this.changeWeather = this.changeWeather.bind(this);
}
render(){
const {isHot,wind} = this.state;
return <h1 onClick={this.changeWeather}>天气{isHot ? 'Hot' : 'cool'}</h1>;
}
changeWeather(){
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
}
root.render(<Weather/>);
//ReactDom.render(<Weather/>,document.getElementById('root'));

精简写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Weather extends React.Component{
//Weather的实例对象上添加属性
state = {isHot:false,wind:'slight'};

render(){
const {isHot,wind} = this.state;
return <h1 onClick={this.changeWeather}>天气{isHot ? 'Hot' : 'cool'}</h1>;
};

//在Weather的实例对象上添加方法
changeWeather = ()=>{
//写成箭头函数,让this直接指向Weather的实例对象
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
}

props

展开运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let arr1 = [1,3,5,7,9];
let arr2 = [2,4,6,8,10];

//展开一个数组
console.log(...arr1); // 1 3 5 7 9
//合并数组
let arr3 = [...arr1,...arr2];

//表示参数不确定,将所有传入的参数用一个数组收集起来
//配合array.reduce非常好用
function sum(...numbers){
return numbers.reduce((preValue,currentValue) =>{
return preValue + currentValue;
})
}
console.log(sum(1,2,3,4)); //10

//展开运算符不能展开一个对象,但可以复制对象
let person = {name:'Tom',age:18};
console.log(...person); //报错
let person2 = {...person} //相当于深拷贝一个对象
person.name = 'Jack';
console.log(person2.name); //Tom

//合并对象时相同key后者的value优先
let person3 = {name:'Tom',age:18};
let person4 = {name:'Jack',age:18};
let person5 = {...person3,...person4};
console.log(person5); //{name:'Jack',age:18};
props的使用
  • 在组件标签中直接传入props,然后在render()内取出props放在页面中。
  • 也可以用展开运算符的方式传入组件标签中。
props的限制

引入prop-types.js,只用可以添加定义的类的一个属性propTypes,在这个属性(对象)中对props的类型和必要性进行限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person extends React.Component{
render(){
const {name,age,sex} = this.props;
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.proTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
//func需要特别记住
speak:PropTypes.func,
}
//指定默认标签属性值
Person.defaultProps = {
sex:'questioned',
age:18
}
props的简写方式

直接使用类的static属性,在类本身上添加属性或方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person extends React.Component{
render(){
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,
//func需要特别记住
speak:PropTypes.func,
}
//指定默认标签属性值
static defaultProps = {
sex:'questioned',
age:18
}
}
类式组件构造器中的props
1
2
3
4
5
6
7
8
9
10
11
12
class Person extends React.Component{
constructor(props){
super(props);
console.log(this.props); //实例上的props能正常输出
}
//如果写构造器的话需要 传props和写super(props)
//但事实上类中的构造器能省略就省略
constructor(){
super();
console.log(this.props); //实例上的props不能正常输出
}
}
函数组件使用props

类式组件的三大属性中只有props是可以在函数组件中使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(props){
const {name,age,sex} = props;
return {
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
}
}
//函数式组件只能写在外面
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
//func需要特别记住
speak:PropTypes.func,
}
//指定默认标签属性值
Person.defaultProps = {
sex:'questioned',
age:18
}
ReactDOM.render(<Person name="jerry" sex="female" age={18}/>,document.getElementById('root'));

refs

跟Vue中的ref蛮像的

回调函数形式的ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Demo extends React.Component{
render(){
return(
<div>
{ /*<input ref={currentNode => this.input = currentNode} type="text"/>*/}
<input ref={this.saveInput} type="text"/>
<button onClick={this.showInfo}>点我</button>
</div>
)
}
showInfo = () =>{
const {input} = this;
alert(input.value);
}
}

Q:在使用内联回调ref时会被调用了几次?

A:如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。(在上述代码中jsx的注释中为ref的内联回调函数写法,然后可以写成class的绑定函数,当页面更新的时候就不会再执行了)

createRef()的使用

React最为推荐的ref的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Demo extends React.Component{
//React.createRef()调用后可以返回一个容器,该容器可以存储被ref所标识的节点
//当input的这个节点用ref标识的时候,就被储存进myRef这个容器中
//该容器是只能储存一个节点的,且当第二次调用的时候会覆盖前一个节点
myRef = React.createRef();
render(){
return(
<div>
<input ref={this.myRef} type="text"/>
<button onClick={this.showInfo}>点我</button>
</div>
)
}
showInfo = () =>{
const input = this.myRef.current;
alert(input.value);
}
}

事件处理

  • 通过onXxx属性指定事件处理函数(注意大小写)。
    • React使用的是自定义(合成)事件,而不是使用原生的DOM事件。
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了更高的效率。
  • 通过event.target得到发生事件的DOM元素。(当发生事件的元素和处理事件的元素是同一个的时候就可以避免使用ref

收集表单数据

相当于vue中的 v-bind和v-model 的区别

非受控组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const {username,password} = this;
console.log(username.value,password.value);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text"/>
密码:<input ref={c => this.password = c} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('root'));

受控组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Login extends React.Component {
state = {
username:'',
password:''
}

saveUsername = (event)=>{
this.setState({username:event.target.value});
}

savePassword = (event)=>{
this.setState({password:event.target.value});
}

handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const {username,password} = this.state;
console.log(username,password);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text"/>
密码:<input onChange={this.savePassword} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('root'));

代码优化

关于事件回调的说明:

1.在事件回调中如果传入参数写成onChange={this.saveFormData(username)}的形式的话,会返回这个函数执行完的返回值作为事件回调。

2.onChange={this.saveFormData}则是将整个函数作为事件回调。

高阶函数:

如果一个函数接受的参数为函数或者调用的返回值为函数,那么这个函数可以被称为高阶函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Login extends React.Component {
state = {
username:'',
password:''
}

saveFormData(dataType){
return (event) => {
//这里在对象中使用变量dataType需要用中括号
this.setState({[dataType]:event.target.value});
}
}

handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const {username,password} = this.state;
console.log(username,password);
}

render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text"/>
密码:<input onChange={this.saveFormData('password')} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('root'));

不使用柯里化的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Login extends React.Component {
state = {
username:'',
password:''
}

saveFormData(dataType,event){
this.setState({[dataType]:event.target.value});
}

handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const {username,password} = this.state;
console.log(username,password);
}

render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={
event => this.saveFormData('username',event)} type="text"/>
密码:<input onChange={
event => this.saveFormData('password',event)} type="password"/>
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('root'));

生命周期(旧)

React-lifecycle-old

  • 初始化阶段:由ReactDOM.render()触发
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()(初始化,如开启定时器、发送网络请求、订阅消息)
  • 更新阶段:由组件内部this.setState()或父组件重新render时触发
    • shouldComponentUpdate(),需要返回truefalse
    • componentWillUpdate()
    • render()
    • componentDidUpdate()
  • 卸载组件:由React.unmountComponentAtNode()触发
    • componentWillUnmount()(收尾工作,关闭定时器、取消订阅消息)

生命周期(新)

删去

  • componentWillUpdate()
  • componentWillMount()
  • componentWillReceiveProps()

新增

  • getSnapshotBeforeUpdate()
  • static getDerivedStateFromProps()

React中proxy

  • package.json中可以直接配置一个代理。

  • src中新建setupProxy.js


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!