Ryan Shang

生死看淡,不服就干

0%

React学习笔记

JSX

​ JSX是react发明的特殊写法语法,JSX=JavaScript+XML,就是JS和HTML的混合写法,这并不是JS引擎原生支持的,所以想在浏览器中直接执行,需要先转成ES5代码。jsx本质上就是一个js变量,可以作为参数、函数返回值、也可以用在循环/判断语句中。

1
2
3
4
ReactDOM.render(
<h1 id="msg"><span>hello</span><span>world</span></h1>,
document.querySelector('#root')
);

转义成为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1参数是元素类型,2参数是属性对象,3参数是子元素
ReactDom.render(React.createElement(
"h1",
{ id: "msg" },
React.createElement(
"span",
null,
"hello"
),
React.createElement(
"span",
null,
"world"
)
), document.querySelector('#root'));

原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//createElement方法执行后会返回一个虚拟DOM对象
function createElement(type, props, children) {
return {type, props: {...props, children}};
}

//把一个虚拟DOM变成真实DOM并插入容器内部
function render(virtualDOM, container) {
let ele = document.createElement(virtualDOM.type);
for (let attr in virtualDOM.props) {
if (attr === 'children') {
ele.innerHTML = virtualDOM.props[attr];
} else {
ele.setAttribute(attr, virtualDOM.props[attr]);
}
container.appendChild(ele);
}
}

ReactDom渲染过程:

  1. 把jsx元素转成React.createElement方法的调用
  2. createElement会返回一个虚拟DOM对象
  3. render方法负责把虚拟DOM对象转换成真实DOM对象,并插入到容器内部

jsx规则:

  1. 如果需要换行的话,需要把jsx放在小括号里
  2. 如果想在js中显示js变量,需要放入大括号,里面可以放入js表达式
  3. 表达式中不能放对象,可以放字符串、数字等,还可以放函数的调用

jsx react元素的属性:

  1. 普通属性
  2. 特殊属性 class => className for => htmlFor
  3. 如果属性名是多个单词的话,驼峰命名法 z-index => zIndex tab-index => tabIndex
  4. react元素有一个非常重要的属性叫children,指这个元素的所有子元素

jsx设置事件:

​ 给react元素设置属性 on+事件名,事件名开头大写。

1
<div onClick={fn}>

ref:

​ ref的值是一个函数,当这个DOM挂载到页面之后会执行绑定的函数,参数就是此react元素对应的真实DOM元素。

1
2
//当这个input的虚拟DOM转成真实的并插入到页面中后,会调用元素的ref函数,并且把这个真实DOM作为参数传到函数中
<input type="text" id="username" required ref={input => {console.log(input)}}/>

组件

​ 组件就像一个纯函数,接受任意参数,返回一个并且只有一个react元素。声明组件有两种方式:函数组件和类组件。

渲染组件的过程:

  1. 初始化属性对象,然后调用类的构造函数,并把类的属性对象传进去,得到组件的实例
  2. 会调用实例的render方法,得到返回的react元素
  3. render方法会把react元素渲染成真实的DOM元素,并挂载到容器内部

函数式组件:

       1. 接受一个props参数,是一个对象
     2. 组件名称必须首字母大写,render只能通过首字母判断是元素还是组件(组件和元素使用方式完全相同,但渲染和使用方式不同,元素之间渲染,组件渲染返回值)
     3. 组件函数要返回并且只能返回一个顶级react元素
     4. 函数组件没有实例,也没有this
1
2
3
4
5
6
7
function Welcome(props) {
if (props.username) {
return <h1>hello {props.username}</h1>
} else {
return <h1>hello stranger</h1>
}
}

类组件:

  1. 必须继承自React.Component
  2. 类组件是有实例的,需要通过this来调用属性
  3. 传给组件的属性会全部封装到一个对象中作为实参传给组件对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Welcome extends React.Component {
render() {
if (this.props.username) {
return <h1>hello {this.props.username}</h1>
} else {
return <h1>hello stranger</h1>
}
}
}

class App extends React.Component{
render(){
return (
<div>
<Welcome username="Ryan"/>
<Welcome username="Jarvan"/>
<Welcome username="Wen"/>
</div>
)
}
}

组件的属性和状态:

  1. 组件的属性是由父组件传入的,状态的值时内部初始化的
  2. 组件的属性不能修改, 状态的值是可以修改的
  3. 组件的值自己不能改,但是父组件可以改
  4. 组件的状态是内部初始化的,只能组件内部修改,外部不能修改
  5. 组件的属性和状态都是当前组件的数据源
  6. 组件的属性是可以自上而下流动的。单项数据流,只能父传子,只能子传父,也不能兄弟之间传递

给组件传入属性对象的一种方式:

1
ReactDOM.render(<Comment {...comment}/>, document.querySelector('#root'));

组件中循环列表:

​ 遍历列表需要给每一项设置key属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const lessons = [
{title: 'Lesson 1: title', description: 'Lesson 1: description'},
{title: 'Lesson 2: title', description: 'Lesson 2: description'},
{title: 'Lesson 3: title', description: 'Lesson 3: description'},
{title: 'Lesson 4: title', description: 'Lesson 4: description'}
];

class LessonsList extends React.Component {
render() {
return (
<div>
{this.props.lessons.map((item, index) => {
return (
<Lesson lesson={item} index={index} key={index}/>
)
})}
</div>
)
}
}

state

​ 组件的状态用来描述组件内部可以变化的数据。组件的状态是类组件所特有的。当类实例化的时候,会自动调用构造函数,我们可以在构造函数中初始化状态对象。

1
2
3
4
5
6
7
class Clock extends React.Component {
constructor(props) {
super(props); //super指向父类的构造函数 this.props=props
//当一个类继承另一个类时,需要先调用父类的构造函数
this.state = {time: new Date()}
}
}

​ 修改state需要掉用实例的setState方法,setState方法用来修改状态,传入一个增量对象,会覆盖同名属性或增加新属性,不会删除老的属性。调用setState不但会修改状态,还会重新调用render方法。除了构造函数外,永远不要直接操作state。

​ 为了提高性能,react可能会把多个setState调用合并成一个。

1
2
3
componentDidMount() {
this.setState({time: new Date()});
}

​ 注意,setState方法是一个异步方法

1
2
3
4
5
6
7
8
9
10
11
this.setState((prevState) => ({number: prevState.number + 1}));
this.setState((prevState) => ({number: prevState.number + 2}));
this.setState((prevState) => ({number: prevState.number + 3}));
//等价于 =>
this.setState({number: this.state.number+1},()=>{
this.setState({number: this.state.number+2},()=>{
this.setState({number: this.state.number+3},()=>{
console.log(this.state.number);
});
});
});

兄弟间传递数据——状态提升:

​ 要让两个兄弟之间传递数据,需要找到他们最近的共同祖先,然后在共同祖先组件中定义一个特权函数,子孙组件通过这个共同祖先组件的特权方法,可以改变这个共同祖先的state状态。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Input extends React.Component {
handleChange(event) {
let percent = event.target.value;
this.props.setPercent(percent);
}

render() {
//给一个输入框一个value值,会变成只读的
return (
<div>
<input type='number' onChange={this.handleChange.bind(this)} value={this.props.percent}/>
</div>
)
}
}

class PercentageShower extends React.Component {
render() {
return (
<div>
{
(parseFloat(this.props.percent)*100).toFixed(2)+'%'
}
</div>
)
}
}

//要让两个兄弟之间传递数据,需要找到他们最近的共同祖先
class PercentageApp extends React.Component {
constructor() {
super();
this.state = {percent: 0.00};
}

//定义了一个可以改变父组件中状态对象中percent的方法
setPercent = (percent) => {
this.setState({percent: percent});
};

render() {
return (
<div>
<Input percent={this.state.percent} setPercent={this.setPercent}/>
<PercentageShower percent={this.state.percent}/>
</div>
)
}
}

生命周期

componentWillReceiveProps: 将要接收属性

componentWillMount: 组件将要挂载

componentDidMount: 组件完成挂载

componentWillUpdate: 组件即将更新

componentDidUpdate: 组件完成更新

componentWillUnmount: 组件即将销毁

shouldComponentUpdate: 询问组件是否更新

1
2
3
4
5
6
7
8
shouldComponentUpdate(prevProps, prevState) {
console.log('询问组件是否更新');
if (prevState.number < 10) {
return true;
} else {
return false;
}
}

Router

​ Router是路由的容器。

开始使用

​ 安装react-router-dom

1
$ npm install react-router-dom

​ 引入react-router-dom并使用

1
2
3
4
5
6
7
8
9
import {HashRouter as Router,} from 'react-router-dom';
//需要使用路由功能的部分需要被Router组件包裹起来
ReactDOM.render(
<Router>
<div>
...
</div>
</Router>, document.querySelector('#root')
);

Router组件会给他的所有子组件传递三个属性:

  1. history 是用来操作历史

    • goBack 返回上一个路径
    • push 跳转路径
  2. location 路径

    • pathname 路径名
    • state 路径状态
  3. match 匹配上有值,匹配不上为null

    • isExact 是否精确匹配

    • params 路径参数

    • path 来自于路由里的path属性

    • url 来自于url地址中的#

      当有路径参数的时候path和url不一样

Route

Route是路由规则

基本使用

使用时,首先使用Link组件实现跳转(类似于Vue中的router-link),然后使用Route组件,通过传入path和component属性,匹配路径默认匹配前缀,只要当前url路径和path的前缀相同就表示能匹配上,然后渲染对应的组件(一组Route类似于Vue中的router-view)。

需要精确匹配,需要给Route组建设置exact属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
HashRouter as Router,
Route,
Link
} from 'react-router-dom';
//path 路由
//component 组件名称
ReactDOM.render(
<Router>
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/user">用户</Link></li>
<li><Link to="/profile">个人</Link></li>
</ul>
<Route exact path="/" component={Home}/>
<Route path="/user" component={User}/>
<Route path="/profile" component={Profile}/>
</div>
</Router>, document.querySelector('#root')
);

Link组件中的to属性也可以写作一个对象

1
2
3
4
5
6
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}}/>

Switch

如果希望最多指向一个组件,则需要把若干个路由由switch包裹起来。

1
2
3
4
5
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/user" component={User}/>
<Route path="/login" component={Login}/>
</Switch>

路由组件渲染三种方法

  • component = 组件
1
<Route path="/" component={Home}/>
  • render 是一个函数,返回值是一个组件,路径匹配则渲染

​ 因为返回的组件不是Router组件的子组件,所以没有Router组件传递过来的三个属性,可用withRouter组件解决,也可直接给返回的组件传入三个属性

1
2
3
4
//rest={path:"/profile"}
<Route {...rest} render={({location,history,match}) => (
<Profile history={history}/> :
)}/>
  • children 是一个函数,返回一個React DOM元素,无论匹不匹配都渲染
1
2
3
<Route path={to} children={({match}) => (
<li className={match ? "active" : ""}><Link to={to}>{children}</Link></li>
)}/>

URL参数

在Route中,设置路径参数,在Link组件中直接访问按照需要的路径参数跳转链接即可。

1
2
3
<Route path="/user/detail/:id" component={UserDetail}/>

<Link to={{pathname: `/user/detail/${user.id}`, state: {user}}}>{user.username}</Link>

认证保护路由

受保护路由是指如果当前用户未登录,则不能访问被保护的路由。

原理是利用Route组件的render渲染方式,根据条件判断渲染不同组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
//ProtectedRoute.js

export default ({component: Component, ...rest}) => {
return (
<Route {...rest} render={({location}) => (
localStorage.getItem('login') ? <Component/> :
<Redirect to={{pathname: "/login", state: {from: location.pathname}}}/>
)}/>
)
}

//使用时
<ProtectedRoute path="/profile" component={Profile}/>

自定义链接

利用Route组件的children渲染方式,对组件进行操作。

1
2
3
4
5
6
7
8
//MenuLink.js

export default ({to, children,}) => {
return (
<Route exact path={to} children={({match}) => (
<li className={match ? "active" : ""}><Link to={to}>{children}</Link></li>
)}/>
)};

阻止导航

使用Prompt组件,当when属性的值为true的时候,弹出confirm框。

1
2
3
4
5
import {Prompt} from 'react-router-dom';
<Prompt
when={this.state.isEditing}
message={location => `切换到${location.pathname}?`}
/>

未匹配

一般放在路由规则最后,配合Switch组件使用,用于404场景。

1
<Route component={NoMatch}/>