知识点
8、React的生命周期函数
9、React生命周期函数的使用场景
10、使用Charles实现本地数据
11、React中实现CSS过渡动画
12、React中使用CSS动画效果
13、使用react-transition-group实现动画
14、react-transition-group的使用
8、React的生命周期函数
定义:生命周期函数指在某一个时刻组件会自动调用执行的函数。
生命周期函数:(如下图)
1)初始化Initialization:初始化数据props and state。
2)挂载组件Mounting:componentWillMount()在组件即将被挂载到页面的时刻,自动执行,只会在第一次挂载执行; render()执行组件挂载到页面;componentDidMount()在组件被挂在到页面之后,自动执行,只会在第一次挂载执行。
3)组件更新Updation:
props发生变化:componentWillReceiveProps()当一个组件从父组件接受参数props,只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被自动执行(换句话说,如果这个组件第一次存在于父组件中,不会被执行,如果这个组件之前已经存在于父组件中,才会执行);之后内容跟states的一样。
states发生变化:shouldComponentUpdate()组件被渲染一次之后,需要进行更新之前,会被自动执行,必须返回一个Boolean值(返回true,组件需要被更新;返回false,组件不需要被更新,不会执行后续周期函数);componentWillUpdate()在组件被更新之前,会被自动执行,但是它在shouldComponent() 之后被执行,如果shouldComponent() 返回true才被执行,如果返回false,则不会被执行;render()重新进行页面更新;componentDidUpdate()组件更新完成之后,它会被执行。
4)卸载组件Unmounting:componentWillUnmount()当这个组件即将被从页面中剔除的时候,自动执行。
9、React生命周期函数的使用场景
1)shouldComponentUpdate()使用,提升组件性能
在TodoList组件中,每次输入内容,由于更改了state.inputValue的值,使得render()会进行重新渲染,父组件重新渲染,子组件TodoItem也进行重新渲染。但是在没有点击提交前,子组件TodoItem的内容并不需要更新。因此,会造成性能上的浪费。优化性能的方法,使用shouldComponentUpdate()来判断是否需要更新。shouldComponentUpdate()在子组件被渲染一次之后,如果需要更新,则通过返回true/false来判断是否更新。
回顾性能优化知识点:a. 在constructor函数中this.handleClick = this.handleClick.bind(this);改变作用域,保证函数作用域绑定操作只执行一次,而且避免子组件的无谓的渲染;b. setState()是异步函数,底层内置了许多性能机制,可以把多次的数据修改合并成一次修改,降低虚拟DOM的比对次数;c. react底层使用了虚拟DOM ,还有同层比对、配值等概念,从而提升react性能;d. shouldComponentUpdate()使用
//此组件被渲染一次之后,如果需要更新,强制要求不更新
//nextProps表示接下来props变成什么样
//nextState表示接下来props变成什么样
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.content !== this.props.content) {
return true;
} else {
return false;
}
}
2)componentDidMount()的使用
假设需要向后台发起ajax请求数据,需要写在哪个生命周期函数比较合适?a.写在render()不好的。因为容易出现死循环,每次输入数据,就会重新执行render(),则重新发起一次数据请求,浪费资源;b. 写在componentDidMount()函数里。因为componentDidMount()只被执行一次,在组件被挂载到页面时被执行一次。为什么不写在componentWillMount()函数,它也只被执行一次,所以把ajax数据请求写在这里也是可以的,但是在使用React Native或者使用react做服务器端的同构等更深的技术,容易产生技术冲突。所以写在componentDidMount()函数最为合理。
安装axios模块:打开cmd,输入执行以下命令后,完成安装
cd <项目文件夹路径>
npm install axios --save
TodoList.js(添加新内容)
...
import axios from 'axios';
...
componentDidMount() {
axios.get('/api/todolist')
.then(() => {
alert('succ')
})
.catch(() => {
alert('error')
});
}
...
10、使用Charles实现本地数据mock
由于在实际开发中,一般进行前后端分离,通过接口来进行数据交互。所以我们使用charles-proxy来进行模拟数据接口。Charles能够抓取浏览器发起的请求,然后做出一些处理和返回。
1)安装charles和注册
charles下载:https://www.charlesproxy.com/latest-release/download.do
一直下一步下一步进行安装。
注册账号(不注册的话,30天会过期,每30s会关闭一次)
Registered Name:https://zhile.io
License Key: 48891cf209c6d32bf4
2)配置接口
a. 创建一个json文件,命名为TodoList(名字可自行更改)
TodoList.json
{
"data": [1, 2, 3]
}
b. 打开charles,点击 Tools -> Map Local …,勾选Enable Map Local,点击Add按钮,按下图填写,其中Local Path选择刚才创建好的json文件。
出错点:
Host选项如果只写localhost,会出现浏览器访问时出现404,经过查看发现charles能抓取线上的包,但是不能抓取本地的包,那么把我们想要访问的接口挂到localhost.charlesproxy.com:3000域名下,就能访问了。
c. 修改TodoList.js文件代码的访问地址
...
componentDidMount() {
axios.get('http://localhost.charlesproxy.com:3000/api/todolist')
.then(() => {
alert('succ')
})
.catch(() => {
alert('error')
});
}
...
出错点:
虽然ajax访问接口能正常的获取到数据,并成功得到202,但是返回结果仍为error,查看以下错误Access to XMLHttpRequest at 'http://localhost.charlesproxy.com:3000/api/todolist' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.,发现出现跨域问题。
charles如何配置跨域?
查看跨域资源共享 CORS 详解 https://blog.csdn.net/xxm0720/article/details/79795715
介绍:OPTIONS请求是非简单请求的一种处理方式。在真正发送请求之前,增加一次HTTP查询请求,称为"预检"请求(preflight),就是我们刚刚说到的参数为OPTIONS的第一次请求,它的作用是:询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP请求和头信息字段。只有得到肯定答复,浏览器才会发出正式的HttpRequest(GET、POST等)第二次请求;否则就报错,也不会进行第二次请求。
解决方法:使用charles的rewrite修改options请求的response header
a. 点击Tools --> Rewrite;
b. 添加rewrite配置
c. 修改header
修改四个字段,具体字段内容如下:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Headers: Accept,Origin,X-Requested-With,Content-Type,Last-Modified
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
d. 修改status code
e. 重新查看页面,可以发现能够成功返回”succ"。
3)动态加载数据显示到页面
加载获取到数据后,修改state数据,达到动态修改数据。查看页面,能够进行正常的增删操作。
TodoList.js(添加新内容)
...
import axios from 'axios';
...
componentDidMount() {
axios.get('http://localhost.charlesproxy.com:3000/api/todolist')
.then((res) => {
console.log(res.data);
this.setState(() => ({
list: [...res.data]
}));
})
.catch((e) => {
console.log(e);
});
}
...
11、React中CSS过渡动画
实现react最基础的动画样式
CssTransition1.js(新建js文件)
import React, {
Component,
Fragment
} from 'react';
import './css/CssTransition1.css';
class CssTransition1 extends Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
this.state = {
show: true
}
}
render() {
return (
<Fragment>
<div className={this.state.show?'show':'hide'}>hello</div>
<button onClick={this.handleToggle}>toggle</button>
</Fragment>
);
}
handleToggle() {
this.setState({
show: this.state.show ? false : true
});
}
}
export default CssTransition1;
CssTransition1.css(新建css文件)
.hide{
opacity: 0;
transition: all 1s ease-in;
}
.show{
opacity: 1;
transition: all 1s ease-in;
}
index.js(修改js文件)
...
// import App from './App';
// import TodoList from './TodoList';
import CssTransition1 from './CssTransition1';
...
// ReactDOM.render(<App />, document.getElementById('root'));
// ReactDOM.render(<TodoList />, document.getElementById('root'));
ReactDOM.render(<CssTransition1 />, document.getElementById('root'));
...
12、React中使用CSS动画效果
CssTransition1.css(修改css文件)
.show{
font-size: 30px;
font-weight: 700;
opacity: 1;
/*transition: all 1s ease-in;*/
/*forwards在动画结束的最后一帧,保存动画的css样式*/
animation: show-item 2s ease-in forwards;
}
.hide{
font-size: 30px;
font-weight: 700;
opacity: 0;
/*transition: all 1s ease-in;*/
/*forwards在动画结束的最后一帧,保存动画的css样式*/
animation: hide-item 2s ease-in forwards;
}
@keyframes show-item{
0%{
opacity: 0;
color: red;
}
50%{
opacity: 0.5;
color: green;
}
100%{
opacity:1;
color: blue;
}
}
@keyframes hide-item{
0%{
opacity: 1;
color: red;
}
50%{
opacity: 0.5;
color: green;
}
100%{
opacity: 0;
color: blue;
}
}
12、使用react-transition-group实现动画
react第三方模块react-transition-group可以实现更复杂的动画效果。
具体可查看官方文档 http://reactcommunity.org/react-transition-group/
1)安装第三方模块react-transition-group
打开cmd,输入以下命令
cd <项目文件夹路径> //进入项目文件夹目录下
npm install react-transition-group --save
2)CSSTransition使用
CSSTransition标签的in属性值为{this.state.show},表示何时要执行动画,无非就是show的值发生改变的时候。 CSSTransition标签的timeout属性,表示动画要执行多久,单位为毫秒; CSSTransition标签的classNames属性,表示一系列class样式的前缀名; CSSTransition标签的unmountOnExit属性,表示隐藏节点时,不占用文档流空间; CSSTransition标签的onEntered钩子,表示在入场结束后能自动执行的函数,可接受参数el,表示内部的节点内容; CSSTransition标签的appear属性值为true,表示当第一次展示在页面上时,也要展示动画效果; 更多属性用法,请查看 http://reactcommunity.org/react-transition-group/css-transition。 如果想实现更多的动画效果,可以使用Transition,具体查看http://reactcommunity.org/react-transition-group/transition
CssTransition2.js(新建js文件)
import React, {
Component,
Fragment
} from 'react';
import {
CSSTransition
} from 'react-transition-group';
import './css/CssTransition2.css';
class CssTransition2 extends Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
this.state = {
show: true
}
}
render() {
return (
<Fragment>
<CSSTransition
in={this.state.show}
timeout={1000}
classNames="fade"
unmountOnExit
onEntered={(el)=>{el.style.color="blue";}}
appear={true}
>
<div>hello</div>
</CSSTransition>
<button onClick={this.handleToggle}>toggle</button>
</Fragment>
);
}
handleToggle() {
this.setState({
show: this.state.show ? false : true
});
}
}
export default CssTransition2;
CssTransition2.css(新建css文件)
/*className可以使用fade前缀*/
/*fade-enter:入场动画的第一帧,准备入场*/
/*fade-appear:将其挂载到页面的入场动画的第一帧,准备入场*/
.fade-enter,.fade-appear {
opacity: 0
}
/*fade-enter-active:开始入场到入场结束前的动画*/
/*fade-appear-active:将其挂载到页面的第一次开始入场到入场结束前的动画*/
.fade-enter-active,.fade-appear-active {
opacity: 1;
transition: opacity 1s ease-in;
}
/*入场结束后的样式*/
.fade-enter-done {
opacity: 1;
}
/*出场动画执行前*/
.fade-exit{
opacity: 1;
}
/*出场动画执行时*/
.fade-exit-active{
opacity: 0;
transition: opacity 1s ease-in;
}
/*出场动画结束后*/
.fade-exit-done{
opacity: 0;
}
14、react-transition-group的使用
如果想要实现多个元素的动画效果,该如何做?
使用TransitionGroup配合CSSTransition来使用,TransitionGroup写在所有需要添加动画的组件外部,CSSTransition写在一个具体的需要动画的组件外部。
CssTransition3.js(新建js文件)
import React, {
Component,
Fragment
} from 'react';
import {
CSSTransition,
TransitionGroup
} from 'react-transition-group';
import './css/CssTransition3.css';
class CssTransition3 extends Component {
constructor(props) {
super(props);
this.handleAddItem = this.handleAddItem.bind(this);
this.state = {
list: []
}
}
render() {
return (
<Fragment>
<TransitionGroup>
{
this.state.list.map((item,index)=>{
return (
<CSSTransition
timeout={1000}
classNames="fade"
unmountOnExit
onEntered={(el)=>{el.style.color="blue";}}
appear={true}
key={index}
>
<div>item</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.handleAddItem}>add</button>
</Fragment>
);
}
handleAddItem() {
this.setState((prevState) => {
return {
list: [...prevState.list, 'item']
}
});
}
}
export default CssTransition3;
CssTransition3.css(新建css文件)
/*className可以使用fade前缀*/
/*fade-enter:入场动画的第一帧,准备入场*/
/*fade-appear:将其挂载到页面的入场动画的第一帧,准备入场*/
.fade-enter,.fade-appear {
opacity: 0
}
/*fade-enter-active:开始入场到入场结束前的动画*/
/*fade-appear-active:将其挂载到页面的第一次开始入场到入场结束前的动画*/
.fade-enter-active,.fade-appear-active {
opacity: 1;
transition: opacity 1s ease-in;
}
/*入场结束后的样式*/
.fade-enter-done {
opacity: 1;
}
/*出场动画执行前*/
.fade-exit{
opacity: 1;
}
/*出场动画执行时*/
.fade-exit-active{
opacity: 0;
transition: opacity 1s ease-in;
}
/*出场动画结束后*/
.fade-exit-done{
opacity: 0;
}
TodoList.js(代码记录)
import React, { Component, Fragment } from 'react';
import TodoItem from './TodoItem'
import axios from 'axios'
import './style.css'
class TodoList extends Component {
constructor (props){
super(props);
this.state = {
inputValue : '',
list: []
}
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
}
render() {
return (
<Fragment>
<div>
<label htmlFor='insertArea'>输入内容</label>
<input
id='insertArea'
className='input'
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => {this.input = input}}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{ this.getTodoItem() }
</ul>
</Fragment>
)
}
componentDidMount(){
axios.get('http://localhost.charlesproxy.com:3000/api/todolist')
.then( (res) => {
console.log(res.data)
this.setState(()=>({
list:[...res.data]
}))
})
.catch( (e) => {
console.log(e)
})
}
getTodoItem(){
return this.state.list.map((item,index) => {
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e){
const value = e.target.value
this.setState(() => ({
inputValue:value
}))
}
handleBtnClick(){
this.setState((prevState) => ({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}))
}
handleItemDelete(index){
this.setState((prevState) => {
const list = [...prevState.list];
list.splice(index,1)
return {list}
})
}
}
export default TodoList;