爱生活,爱编程,学习使我快乐
在chrome浏览器中安装React developer tools插件
打开Chrome网上应用店需要翻墙。
如果不翻墙,可以自己下载谷歌react的crx版的插件,点击更多工具,扩展程序,然后将crx文件拖入界面中就可以了。(待验证)
安装成功后,浏览器右上角会出现React图标。F12打开工发者工具,右侧也会出现一个React选择。打开后会有组件结构、Props、State等信息。
浏览器右上角如果为红色,则说明现在打开的是React开发的项目,而且在开发环境运行。
浏览器右上角如果为黑色,则说明现在打开的是React开发的线上项目。
浏览器右上角如果为灰色,则说明现在打开的不是React开发的项目。
PropTypes设置prop的类型,DefaultProps设置默认值。
import PropTypes from 'prop-types';
TodoItem.propTypes = {
test: PropTypes.string.isRequired,
// 类型是数字或者是字符串
content: PropTypes.oneOfType([PropTypes.number, PropTypes,string]),
deleteItem: PropTypes.func,
index: PropTypes.number
}
TodoItem.defaultProps = {
test: 'hello world'
}
当父组件的render函数被运行时,它的子组件的render都会被重新执行
接下来我们来尝试分析应该如何渲染数据和模板。
todoList为例:当input框内容改变时,页面重新渲染。
缺陷:
1. 第一次生成了一个完整的DOM片段
2. 第二次又生成一个完整的DOM片段
3. 第二次的替换第一次的,非常耗性能。
总之:操作DOM都比较耗性能。
缺陷: 性能提升的并不是很明显。
<div><span> hello world </span></div>
['div', {id: 'abc'}, [ 'span', {}, 'hello world' ] ]
['div', {id: 'abc'}, [ 'span', {}, 'bye bye' ] ]
- 因减少了DOM操作,所以极大提升了性能。
- 因虚拟DOM本质是JS对象,所以作对比的时候性能消耗很少,也提升了性能。
虚拟DOM说明:虚拟DOM是一个JS数组,有三项,格式如下
[DOM元素节点名称, 节点属性(属性对象), 子项(又可以是一个虚拟DOM 或 内容)]
上节我们分析了虚拟DOM的渲染,找出了最优的解决方案–方法三。但实际情况中应该是如下顺序。(3、4步骤是反过来的,应该先生成虚拟DOM,再生成真实的DOM)
数据 + 模版 结合 生成虚拟DOM (虚拟DOM其实就是一个js对象,用它来描述真实的DOM)
['div', {id: 'abc'}, [ 'span', {}, 'hello world' ] ]
<div><span> hello world </span></div>
['div', {id: 'abc'}, [ 'span', {}, 'bye bye' ] ]
JSX -> React.createElement -> 虚拟 DOM(JS 对象) -> 真实的 DOM
JSX的写法
return <div><span>item</span></div>
React.createElement的写法
return React.createElement('div', {}, React.createElemtent('span', {}, 'item'))
数据发生改变的时候才会产生diff 算法。(state 或者 props 发生改变的时候,也就是调用 setState的时候)
虚拟DOM是同级比对的,如果比对有差异,下面子级的节点就不再作比对,就会把这层以下的所有的DOM全部替换为新的虚拟DOM。
虚拟DOM在循环的时候,要给定一个Key值。而且要是唯一的,不要使用index(索引)作为Key值。
// 如果用index 去对应key值。
// 原始状态key值对应如下:
a: 0, b: 1, c: 2
// 如果进行删除操作之后,如删掉a。那么这个时候数据就变成了b c
// 现在key值对应如下:
b: 0 c: 1,
// 这样子每项对应的Key值就完全变掉了,那这个key值就是失去来存在的意议。
ref是refrence(引用)的缩写。Refs 提供了一种方式,允许我们访问 DOM 节点。
<input ref={(inputRef) => {this.input = inputRef}} />
setState( () => ({}), () => {} )
// 第一个参数是设置新的state,第二个参数是setState完成后的回调函数。
// 如果想获取state更新后的DOM节点,就必须写在回调函数里。
完整例子如下:
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(inputRef) => {this.input = inputRef}}
/>
// 使用以上ref,则可以直接使用this.input存储input的DOM节点的引用
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul ref={(ulRef) => this.ul = ulRef}>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<TodoItem
key={item}
content={tiem}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e) {
// const value = e.target.value
// 使用了ref后,this.input等价于e.target
// 但是不建议使用ref这种方法,尽量使用以数据驱动的方式编写代码,不要操作DOM元素。而且这样子做有时还会出现一些bug。
const value = this.input
this.setState(() => ({
inputValue: value
}))
}
handleBtnClick() {
/*this.setState((prevState) => {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
})
// 这样子打印出的长度其实要比实际的少一个。
// 看似setState已经执行,但是setState是个异步函数,会在console.log()之后执行。
console.log(this.ul.querySelectorAll('div').length)*/
// 正确的方式是在setState的第二个参数(回调函数)中打印长度。
this.setState((prevState) => {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}, () => {
console.log(this.ul.querySelectorAll('div').length)
})
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...PrevState.list] // 重新赋值,而不是引用
list.splice(index, 1)
return {list}
})
}
生命周期函数是指组件在某一个时刻自动执行的函数,每个组件中都有生命周期函数。
初始化数据
constructor:这只是一个阶段中执行的函数,不能算是生命周期函数。使用初始化state和props。
挂载阶段
* componentWillMount:组件被挂载到页面前执行(第一次渲染时会被执行)
* render:渲染页面,即:挂载 (第一次渲染时会被执行)
* componentDidMount:组件被挂载到页面后执行(第一次渲染时会被执行)
更新(数据发生变化时)阶段
* componentWillReceiveProps:从父组件传来的Props变化时执行。
组件要从父组件接受参数
只要父组件的render函数重新执行了,子组件的这个生命周期函数就会执行。可细化为以下两句话。
如果这个组件第一次存在于父组件中,不会执行。
如果这个组件之前已经存在于父组件中,才会执行。
注销
* componentWillUnmount:即将注销组件前执行
shouldComponentUpdate(nextProps, nextState){
if(nextProps.content !== this.props.content) {
return true
}else{
return false
}
}
性能优化:
1. 使用生命周期函数shouldComponentUpdate(nextProps,nextState)阻止不必要的子组件渲染,提高性能。
2. 作用域的修改:把尽量使用的操作放在constructor里面。比如:this.handleClick.bind(this),只会执行一次,避免子组件无谓渲染。
3. React 的底层setState,内置性能提升机制,异步函数,把多次数据改变结合一一次来做,降低虚拟DOM比对频率。
4、React底层用的是虚拟DOM的概念,同层比对,还有key 值这样的概念,提升虚拟DOM比对速度。
1、ajax请求只需求请求一次,所以适用放在只执行一次的函数中。如:componentWillMount或constructor中
2、如果放在render函数中,会形成死循环。
3、但在更高级的使用时(如:使用React Native时),放在componentWillMount中会出问题。
4、约定ajax请求最好是放在componentDidMount生命周期函数中,放这里肯定没有问题。
axios的安装和使用:
安装
npm install axios --save-dev
使用(放在componentDidMount生命周期函数中)
import axios from 'axios'
componentDidMount() {
axios.get('./api/todolist')
.then((res) => {
this.setState(() => ({
list: [...res.data]
}))
})
.catch(() => {console.log("erro")})
}
Charles是一个HTTP代理/ HTTP监视器/反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量。这包括请求,响应和HTTP标头(包含cookie和缓存信息)。
Charles 是在 Mac 下常用的网络封包截取工具,在做移动开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析。
Charles 通过将自己设置成系统的网络访问代理服务器,使得所有的网络访问请求都通过它来完成,从而实现了网络封包的截取和分析。
除了在做移动开发中调试端口外,Charles 也可以用于分析第三方应用的通讯协议。配合 Charles 的 SSL 功能,Charles 还可以分析 Https 协议。
Charles 主要的功能包括:
打开映射规则面板
填写映射配置信息(当请求配置的地址时,把本地选择的文件内容当作返回数据返回)
需要勾选这两者
安装react-transition-group模块
npm install react-transition-group --save
react-transition-group包含三个
* Transition
* CSSTransition
* TransitionGroup
import React, { Component, Fragment } from 'react'
import { CSSTransition } from 'react-transition-group';
class App extends Component {
constructor(props) {
super(props);
this.state = {
show: true
}
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<Fragment>
<CSSTransition
in={this.state.show} // 如果this.state.show从false变为true,则动画入场,反之out出场
timeout={1000} // 动画执行1秒
classNames="fade" // 自定义的class名
unmountOnExit // 可选,当动画出场后在页面上移除包裹的dom节点
//可选,动画入场之后的回调,el指被包裹的dom,让div内的字体颜色等于蓝色
onEntered={(el) => {
el.style.color='blue'
}}
//同理,动画出场之后的回调,也可以在这里来个setState啥的操作
onExited={() => {
// ……
}}
appear={true} // 第一次入场就执行动画
>
<div>hello</div>
</CSSTransition>
<button onClick = {this.handleToggle}>toggle</button>
</Fragment>
)
}
handleToggle() {
this.setState({
show: this.state.show ? false : true;
})
}
}
对应的样式
/* enter是入场前的刹那(点击按钮),appear指页面第一次加载前的一刹那(自动) */
.fade-enter, .fade-appear{
opacity: 0;
}
/* enter-active指入场后到入场结束的过程,appear-active则是页面第一次加载自动执行 */
.fade-enter-active, .fade-appear-active {
opacity: 1;
transition: opacity 200ms;
}
/* 入场动画执行完毕后,保持状态 */
.fade-enter-done {
opacity: 1;
}
/* 同理,出场前的一刹那,以下就不详细解释了,一样的道理 */
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 200ms;
}
.fade-exit-done {
opacity: 0;
}
import React, { Component, Fragment } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: []
}
this.handleAddItem = this.handleAddItem.bind(this);
}
render() {
return (
<Fragment>
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
timeout={1000}
classNames="fade"
unmountOnExit // 元素隐藏后DOM被移除
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, 'iiemss']
}
})
}
}
对应的样式
.fade-enter, .fade-appear{
opacity: 0;
}
.fade-enter-active, .fade-appear-active {
opacity: 1;
transition: opacity 200ms;
}
.fade-enter-done {
opacity: 1;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 200ms;
}
.fade-exit-done {
opacity: 0;
}