虚拟DOM
render函数中返回的看似html的jsx语法模板会经过React.createElement()生成一个虚拟DOM,然后虚拟DOM再渲染真实DOM。
在虚拟dom进行比对的时候react提供了diff算法,以下关于diff算法的几个记点:
1、setState()修改数据就会触发虚拟dom比对,也就是运用diff算法,但连续的多次setState会被合并成一次setState,也就是只进行一次虚拟dom比对,这是一个性能优化。
2、虚拟dom比对时是同层级比对,如果第一层比对就不一样那么就不会再往下进行比对,react会将原来的dom全部销毁生成新的dom进行替换。这样做可能会有渲染dom的一些浪费,但同层比对会使diff算法性能高。
3、循环遍历的元素上要加key,且是稳定唯一的值,不要用index(因为不稳定),这样是为了进行比对时确定两个虚拟dom中对应的节点。
下面来看几张图分析一下
上图所示,先进行第一层比对R相同,再进行第二层,D和G不同,将D以及D下面的E、F销毁,重新创建G、E、F。
上图所示,列表元素中加了key值,在一系列的移动,移除,添加之后,D销毁,E添加,A、B移动,C不变。
html文本不转义输出
<p dangerouslySetInnerHTML={{__html: '<h2>html输出</h2>'}}></p>
input数据响应
// state
constructor(props){
super(props);
this.state = {
inputValue: 'hello Sam'
}
}
{/* html */}
<label htmlFor="input">请输入</label>
<input id="input" type="text" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)}/>
// handle
handleInputChange(e){
this.setState({
inputValue: e.target.value
})
}
上面handleInputChange方法也可写成箭头函数 handleInputChange = e => {} ,还有一种推荐写法,将 this.handleInputChange = this.handleInputChange.bind(this) 写进constructor函数里。
数组增减(setState())
react中不允许直接改变state,需要改变数据都要使用setState()。下面看react中怎么改变数组,充分运用es6语法。
handleArray(index){
// 数组后面新增newItem
// this.setState({
// list: [...this.state.list, newItem]
// })
// 数组去除某项
let arr = [...this.state.list];
arr.splice(index, 1);
this.setState({
list: arr
})
}
组件
父组件传递数据给子组件
跟vue一样,通过在子组件标签添加自定义属性的方式。
{/* 父组件 */}
<Child parentMsg={'hahalala'}></Child>
// 子组件
class Child extends Component{
render(){
return (
<div>
<h2>{this.props.parentMsg}</h2>
</div>
)
}
}
子组件传递数据给父组件
实现方法跟上面差不多,就是给子组件标签加属性时传的是一个函数,然后子组件中通过this.props获得这个函数调用即可。
{/* 父组件 */}
<p>{this.state.childMsg}</p>
<Child fromChild={this.handleChildMsg.bind(this)}></Child>
// 父组件
handleChildMsg(str){
this.setState({
childMsg: str
})
}
// 子组件
class Child extends Component{
render(){
return (
<div>
<h2 onClick={this.sendToParent.bind(this)}>hello</h2>
</div>
)
}
sendToParent(){
this.props.fromChild('hello lala');
}
}
props校验
// 引包
import PropTypes from 'prop-types';
// props校验
Child.propTypes = {
prop1: PropTypes.string, // 如果没传就不会检测,所以不会报错
prop2: PropTypes.string.isRequired, //如果没传,会报警告
prop3: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),//可是字符串或者数字
prop4: PropTypes.func,
prop5: PropTypes.array
}
// 默认prop
Child.defaultProps = {
prop1: 'hello world'
}
ref获取dom元素及setState异步
ref获取dom元素
{/* html */}
<input type="text" ref={el => {this.input = el}}/>
// 获取
this.input
和vue一样,在数据变化之后即可操作dom可能会有一些影响,因为此时虽然数据变化了,但还没渲染到页面上。在vue红vue给我们提供了$nextTick方法,而react中可以在setState函数中操作,因为setState实际是个异步函数。setState函数传入第二个参数,是一个回调函数,将dom操作写在回调函数里面。
setState中操作dom
{/* html */}
<input type="text" value={this.state.inputValue} ref={el => {this.input = el}}/>
// js
handleInput(){
this.setState({
inputValue: 'bbb'
}, () => {
console.log(this.input.value)
})
}
生命周期函数
生命周期函数是指在某一时刻组件会自动调用执行的函数。
constructor():本质上来说它也算声明周期,这是es6对类的定义里的默认函数,手动声明constructor时必须调用super方法,在constructor中如果要访问this.props需要传入props 。
componentWillMount():组件挂载之前,只调用一次,react17之后会被移除。
render():相当重要,必须定义,创建虚拟dom,进行diff算法,更新dom树都在此进行。
componentDidMount():组件挂载完成后,只调用一次,此时可获取真实dom元素,推荐将ajax请求写在这里。
componentWillReceiveProps (nextProps):props发生变化以及父组件重新渲染时都会触发该生命周期,在该钩子内可以通过参数nextProps获取变化后的props参数,通过this.props访问之前的props。该生命周期内可以进行setState。React17后会被移除。(React v16.3后可以用新的周期 static getDerivedStateFromProps 代替)
shouldComponentUpdate(nextProps, nextState):是否重新渲染。组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。默认返回true,需要重新render。返回false则不触发渲染。在比较复杂的应用里,有一些数据的改变并不影响界面展示,可以在这里做判断,优化渲染效率。
componentWillUpdate(nextProps, nextState):组件即将更新。shouldComponentUpdate返回true或者调用forceUpdate之后,componentWillUpdate会被调用。不能在该钩子中setState,会触发重复循环。
componentDidUpdate():组件更新完成。除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。该钩子内setState有可能会触发重复渲染,需要自行判断,否则会进入死循环 。一般情况下:里面有定时器和异步请求时不会出现死循环。
componentWillUnmount():组件即将卸载。一般在componentDidMount里面注册的事件需要在这里删除。像在didmount里面设置的定时器可以在这里面进行清除。
动画
react-transition-group
//装包
npm install react-transition-group --save
基本用法
// 引包
import {CSSTransition} from 'react-transition-group';
// state
this.state = {
show: true
}
{/* render() */}
<CSSTransition
in={this.state.show}
timeout={1000}
classNames="fade"
onEntered={el => {el.style.color = 'pink'}}
unmountOnExit
appear={true}>
<h2>hello world</h2>
</CSSTransition>
<button onClick={this.handleToggle.bind(this)}>点我切换动画</button>
/* css */
.fade-enter, .fade-exit-done, .fade-appear{
opacity: 0;
}
.fade-enter-active, .fade-appear-active{
opacity: 1;
transition: all 1s ease-in;
}
.fade-enter-done, .fade-exit{
opacity: 1;
}
.fade-exit-active{
opacity: 0;
transition: all 1s ease-in;
}
效果图
分析一下CSSTransition标签中的属性:
in={this.state.show}:该动画根据this.state.show判断出入场。
timeout={1000}:过渡时间,必写。
classNames="fade":跟vue用法一样,css类名标识。
onEntered={el => {el.style.color = 'pink'}}:动画钩子函数。
unmountOnExit:跟vue中v-if指令效果一样,出场后销毁dom元素。
appear={true}:页面初始化时就启动进场动画,需要加入appear和appear-active样式。
列表动画
// 引包
import {CSSTransition, TransitionGroup} from 'react-transition-group';
// state
this.state = {
list: []
}
// 添加列表函数
addList(){
this.setState({
list: [...this.state.list, '大家好']
})
}
{/* render() */}
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
timeout={1000}
classNames="fade"
onEntered={el => {el.style.color = 'pink'}}
unmountOnExit
appear={true}
key={index}>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.addList.bind(this)}>点我添加列表</button>
css样式跟上例基本用法一样。
效果图
分析一下,在列表动画中主要使用TransitionGroup标签和CSSTransition标签配合,这里CSSTransition标签中就不用in属性了,还有循环列表记得加key,而且这里是加在CSSTransition标签上,这里简单演示一下就用了index,实际应用中应避免使用index当key。
无状态组件
当一个组件中只有一个render函数,那么可以称之为无状态组件,一般常见为UI组件,像这种组件完全可以用一个函数代替,写一个函数传入props,然后返回一段jsx代码,效果完全一样。
function Comp(props) {
return <h1>Hello, {props.name}</h1>;
}
路由
装包
npm install react-router-dom --save
基本使用
根组件应该包在Router标签内,这样等于全局所有组件都受路由管理。
import {
HashRouter as Router
} from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
跟vue不一样的是react路由规则写在组件中,代码中Switch标签区域相当于vue中的router-view区域,但并不是说Switch与router-view相同。还有用不用Switch的区别,NavLink与Link也有出入。组件中this.props.location.query获取query参数(query在Link标签中定义了),this.props.match.params获取params参数。
import {Switch, Route, Redirect, NavLink} from 'react-router-dom';
render(){
return (
<div>
<div className="nav">
{/* params参数 */}
<NavLink to="/com_a/aaa">A组件</NavLink>
{/* query参数 */}
<NavLink to={{pathname: '/com_b', query: {lala: 321}}}>B组件</NavLink>
</div>
<Switch>
<Route path="/com_a/:name" component={ComA} />
<Route path="/com_b" component={ComB} />
<Redirect to="/com_b" />
</Switch>
</div>
)
}
Puppeteer是一个node爬虫很好用的工具。引用文章地址点击打开链接