# Fragment 类似 Vue 中的 template 一样的作用
import React, { Component, Fragment } from 'react'
class TodoList extends Component {
render () {
return (
<Fragment>
<div>
<input />
<button>提交</button>
</div>
<ul>
<li>英语</li>
<li>learning react</li>
</ul>
</Fragment>
)
}
}
export default TodoList
# react中定义数据要放在状态之中
import React, { Component, Fragment } from 'react'
class TodoList extends Component {
constructor(props) {
// 组件继承
super(props)
// react中定义数据要放在状态之中
this.state = {
inputValue: '',
list: []
}
}
render () {
return (
<Fragment>
</Fragment>
)
}
}
export default TodoList
# input 绑定数值
// 数据就是上面代码定义的,跟Vue类似
<input value={ this.state.inputValue }/>
# 绑定事件:onChange
render () {
return (
<Fragment>
<div>
<input value = { this.state.inputValue }
onChange = { this.handleInputChange }
/>
<button>提交</button>
</div>
<ul>
<li>英语</li>
<li>learning react</li>
</ul>
</Fragment>
)
}
绑定点击事件,都跟原生的差不多,onClick
:
onClick = { handleClick }
handleClick () {
const action = {
type: 'add_item'
}
dispatch(action)
},
# 改变状态中的值:跟 vue 还是有很大的区别:
this.setState({
inputValue: e.target.value
})
setState方法也可以写成一个返回异步的函数:
// 写成函数的形式是异步返回的
const inputValue = e.target.value
this.setState(() => {
return {
inputValue
}
})
如果需要在组件的方法中使用this。需要在html绑定方法的时候绑定this:
render () {
return (
<Fragment>
<div>
<input value = { this.state.inputValue }
onChange = { this.handleInputChange.bind(this) }
/>
<button>提交</button>
</div>
</Fragment>
)
}
# for 循环:跟Vue的 v-for 还是有很大的区别
<ul>
{
this.state.list.map((item, index) => {
return <li key={ index }>{ item }</li>
})
}
</ul>
# class的样式添加:
首先通过import引入样式,然后进行绑定样式,注意这里是className进行绑定,为了防止跟class关键字重名:
// 引入样式
import './style.css'
<div>
{ /*这是一个注释*/ }
<input
className = 'input'
value = { this.state.inputValue }
onChange = { this.handleInputChange.bind(this)
}
/>
# 与Vue中 v-html指令类似的:dangerouslySetInnerHTML
return (
<li
key={ index }
onClick={ this.handleItemDelete.bind(this, index) }
dangerouslySetInnerHTML = { { __html: item } }
>
</li>
)
# 如果需要点击一个label标签,然后input获取输入焦点,可以这样写:
htmlFor要对应input的id
<div>
<label htmlFor="insertArea">输入内容</label>
{ /*这是一个注释*/ }
<input
id="insertArea"
className = 'input'
value = { this.state.inputValue }
onChange = { this.handleInputChange.bind(this)
}
/>
<button onClick = { this.handleBtnClick.bind(this) }>提交</button>
</div>
# 组件之间的传值
父组件向子组件传值,跟vue类似,也是通过属性进行传值:
// 父组件
return (
<div>
<TodoItem content = { item }/>
{
/*
<li
key={ index }
onClick={ this.handleItemDelete.bind(this, index) }
dangerouslySetInnerHTML = { { __html: item } }
>
</li>
*/
}
</div>
)
// 子组件接收通过this.props
render() {
return (
<div>{ this.props.content }</div>
)
}
子组件接收父组件传入的值进行校验,跟Vue还是存在很大的区别: 在子组件中首先需要引入prop-types包,
// 传值类型校验
import PropTypes from 'prop-types'
// 类型
TodoItem.propTypes = {
// content 是一个string类型的
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number,
// 表示该值必须要传并为string类型
test: PropTypes.string.isRequired,
// test 有可能是number,有可能是string类型
test: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}
// 默认属性
TodoItem.defaultProps = {
// 设置content默认为空字符串
content: ''
}
子组件向父组件传值: 跟Vue还是有很大的区别,方法也是通过属性的形势进行传递:
// 父组件
<TodoItem
content = { item }
index = { index }
deleteItem = { this.handleItemDelete.bind(this) }
/>
// 子组件
handleClick() {
// 直接通过下面的方式进行调用父组件的方法,
// 注意这里其实就是调用this.handleItemDelete,
// 需要在父组件进行绑定this,在父组件传值的时候需要记得绑定this
this.props.deleteItem(this.props.index)
}
# react 框架的特点
声明式的开发
react 的开发方式是声明式的开发,以前jquery是命令式的开发,直接操作dom。
可以与其他框架并存
react也可以与其他框架并存,因为他只是管理有关挂载点相关的dom渲染。react开发是组件化的开发。
单向数据流
react中是单向数据流;父组件可以向子组件传值,但是子组件不能随意修改父组件的数据。 如果每个子组件都接收父组件的数据,然后每个子组件都修改数据,其他的组件数据全部就会发生改变。 一个数据被很多组件所公用;所以数据是单项数据流;如果必须修改可以父组件向子组件传递方法, 在子组件调用父组件的方法,在父组件中进行数据的改变,这样维护代码比较容易。
视图层框架
非父子组件之间的通讯,按照之前的方法很是麻烦,类似Vue,有vuex进行管理数据。 react使用redux等进行管理数据
函数式编程
维护比较容易,前端自动化测试的时候比较容易;只需要给函数输入值查看结果是否是正确的。
# 虚拟DOM
- state 数据
- JSX模板
- 数据 + 模板结合,生成真实的DOM,来显示
- state 发生改变
- 数据 + 模板,生成真实的DOM,替换原始的DOM
缺陷: 第一次生成了一个完整的DOM片段 第二次生成了一个完整的DOM片段 第二次生成的DOM完全替换第一次生成的DOM;非常消耗性能。
改良:
- state 数据
- JSX模板
- 数据 + 模板结合,生成真实的DOM,来显示
- state 发生改变
- 数据 + 模板,生成真实的DOM,并不直接替换原始的DOM
- 新的 DOM (DocumentFragment)和原始的 DOM 做比对,找差异。
- 找出 input 框发生了的变化
- 只用新的DOM中的input元素,替换掉老的DOM中的input元素
缺陷: 性能的提升并不明显
- state 数据
- JSX模板
- 数据 + 模板结合,生成真实的DOM,来显示
<div id='abc'><span>hello world</span></div>
- 生成虚拟 DOM (虚拟DOM就是一个JS对象,用它来描述真实DOM)
['div', { id: 'abc }, ['span', {}, 'hello world']]
- state 发生改变
- 数据 + 模板,生成新的虚拟 DOM,
['div', { id: 'abc }, ['span', {}, 'bye']]
- 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容
- 直接操作DOM,改变span中的内容
react实现:
- state 数据
- JSX模板
- 数据 + 模板结合,生成虚拟的DOM
['div', { id: 'abc }, ['span', {}, 'hello world']]
- 用虚拟 DOM (虚拟DOM就是一个JS对象,用它来描述真实DOM) 生成真实的DOM,来显示
<div id='abc'><span>hello world</span></div>
- state 发生改变
- 数据 + 模板,生成新的虚拟 DOM,
['div', { id: 'abc }, ['span', {}, 'bye']]
- 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容
- 直接操作DOM,改变span中的内容
我们写的JSX语法也可以使用createElement的方法来实现:
return (
React.createElement('div', {}, 'item')
)
虚拟DOM优点:
- 性能提升了。
- 他使得跨端应用得以实现:React Native
虚拟DOM中的Diff算法:
同层虚拟DOM的比对,如果第一层不一致,就直接替换他的所有的子的DOM。 如果我们渲染数组进行循环渲染,最好不要使用循环的index,因为比如a 0 b 1 c 2, 如果我们删除了a。那么b:0,c:1;会导致key不稳定,diff算法比较会进行重新渲染。
# React中ref的使用
ref获取元素对应的DOM;跟Vue是一样的,但是ref是一个函数:
return (
<input
id="insertArea"
className = 'input'
value = { this.state.inputValue }
onChange = { this.handleInputChange }
ref={ (input) => { this.input = input } }
/>
)
handleInputChange(e) {
// 写成函数的形式是异步返回的
// const inputValue = e.target.value
// this.input指向的是实际的input的DOM元素
const inputValue = this.input.value
this.setState(() => {
return {
inputValue
}
})
}
// 尽量不使用ref进行操作dom。就跟我们在Vue中会使用$nextrick函数一样,setState更新数据是异步的。
// 我们可以使用setState的第二个函数,里面进行获取数值,没有类似$nextrick函数:
handleInputChange(e) {
const inputValue = this.input.value
this.setState(() => {
return {
inputValue
}
}, () => {
console.log(this.ul.querySelectorAll('div').length)
})
}
# React中声明周期函数
初始化:constructor Mounting:挂载数据。componentWillMount、render、componentDidCatch Updation:数据更新 包括组件更新以及states更新: props独有的:componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
Unmounting:把组件从页面去除的过程 componentWillUnmount
// 在组件即将被挂载到页面的时刻自动执行--类似Vue的beforeMounted
componentWillMount() {
console.log('componentWillMount')
}
// 组件被挂在到页面之后,自动被执行 --Vue的mounted函数
// 一般ajax请求放在这里
componentDidMount() {
console.log('componentDidMount')
}
// 组件被更新之前,他会自动被执行,
shouldComponentUpdate() {
// 需要返回一个bool类型,组件数据是否被更新
console.log('shouldComponentUpdate')
return true
}
// 组件被更新之前,他会自动执行,vue的beforeUpdated ,但是他在shouldComponentUpdate之后
// 执行,如果shouldComponentUpdate返回true才会执行,如果返回false,就不会被执行
componentWillUpdate() {
console.log('componentWillUpdate')
}
// 组件更新完成之后,他会被执行
componentDidUpdate() {
console.log('componentDidUpdate')
}
// 当一个组件从父组件接收参数
// 只要父组件的render函数被重新执行了,子组件的这个声明周期就会被执行
// 如果这个组件第一次存在于父组件中,不会执行
// 如果这个组件之前已经存在于父组件中,才会执行。
componentWillReceiveProps() {
console.log('componentWillReceiveProps')
}
// 当这个组件即将被从页面中剔除的时候,会被执行
componentWillUnmount() {
console.log('componentWillUnmount')
}
生命周期函数的使用场景:
提升性能: 比如我们做的todolist,当input输入框中输入的数据没有添加到数组中,也就是要 传递给子组件中,就不让他生成虚拟dom进行渲染(如果不进行控制,每次在input输入值的时候,会执行子组件的render重新渲染) 可以进行判断传值的变化,进行减少性能消耗。
// 组件是否更新,如果传进来的值跟之前的一样,就不进行更新,
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.content !== this.props.content) {
return true
}
return false
}
# React中动画
使用css动画效果:
.show {
opacity: 1;
transition: all 1s ease-in;
}
.hide {
opacity: 0;
transition: all 1s ease-in;
}
或者定义动画:
.show {
animation: show-item 2s forwards;
}
/* 定义 hide-item的动画,持续两秒,ease-in动画曲线,forwards就是最终保留动画的最后效果,不要去除*/
.hide {
animation: hide-item 2s 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;
}
}
使用 react-transition-group 实现动画
https://github.com/reactjs/react-transition-group https://reactcommunity.org/react-transition-group/
使用这个库就跟Vue的动画组件一样,有几个类进行控制动画:
.fade-enter, .fade-appear {
opacity: 0;
}
.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;
}
在元素标签上,需要进行设置:其中onEntered是当动画执行完成之后执行的钩子函数,我们也可以 在fade-enter-done的类中添加color属性,跟onEntered里面设置是一样的
<CSSTransition
// 根据什么属性进行变化
in = { this.state.show }
timeout = { 1000 }
classNames = 'fade'
// unmountOnExit 不显示的时候之间移除dom
unmountOnExit
onEntered = { (el) => {
el.style.color = 'blue'
}}
// 页面在第一次渲染的时候执行动画,会给元素增加fade-appear以及fade-appear-active两个类
appear = { true }
>
<div>hello</div>
</CSSTransition>
使用 react-transition-group 实现多个元素的动画
首先需要引入TransitionGroup:跟单个不同的是,需要在每一个循环的元素外层包裹一个CSSTransition。
import React, { Component, Fragment } from 'react'
import './style.css'
// 动画组件
import {
CSSTransition,
TransitionGroup,
} from 'react-transition-group'
class App extends Component {
constructor(props) {
super(props)
this.state = {
show: true,
list: []
}
this.handleToggle = this.handleToggle.bind(this)
this.handleAddItem = this.handleAddItem.bind(this)
}
render() {
return (
// <Fragment>
// <CSSTransition
// in = { this.state.show }
// timeout = { 1000 }
// classNames = 'fade'
// // unmountOnExit 不显示的时候之间移除dom
// unmountOnExit
// onEntered = { (el) => {
// el.style.color = 'blue'
// }}
// appear = { true }
// >
// <div>hello</div>
// </CSSTransition>
// <button onClick = { this.handleToggle }>toggle</button>
// </Fragment>
<Fragment>
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
key={ index }
timeout = { 1000 }
classNames = 'fade'
// unmountOnExit 不显示的时候之间移除dom
unmountOnExit
onEntered = { (el) => {
el.style.color = 'blue'
}}
appear = { true }
>
<div>{ item }</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick = { this.handleAddItem }>toggle</button>
</Fragment>
)
}
handleToggle() {
this.setState(() => {
return {
show: this.state.show ? false : true
}
})
}
handleAddItem() {
this.setState((prevState) => {
return {
list: [...prevState.list, 'item']
}
})
}
}
export default App
# 子组件调用父组件的方法并传递参数
上面只是写了父组件可以像其他数据一样传递函数给子组件,但是没有像父组件的方法中传递 参数,如果我们这样写:this.props.handleItemDelete(index) 直接传入index是不行的;
// 错误的写法
<List
renderItem = { (item, index) => (
<List.Item
onClick = { this.props.handleItemDelete(index) }
>
{ item }
</List.Item>
)}
/>
// 正确的写法
<List
renderItem = { (item, index) => (
<List.Item
onClick = { (index) => {
this.props.handleItemDelete(index)
} }
>
{ item }
</List.Item>
)}
/>
# 无状态组件
当一个组件只有render函数的时候,我们可以使用无状态组件来替换,例如:
import React, { Component } from 'react'
import { Input, Button, List } from 'antd'
class TodoListUI extends Component {
render() {
return (
<div style = {{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value = { this.props.inputValue }
placeholder="Basic usage"
style = {{ width: '300px', marginRight: '10px'}}
onChange = { this.props.handleInputChange }
/>
<Button
type="primary"
onClick = { this.props.handleBtnClick }
>提交</Button>
</div>
<List
style = {{ marginTop: '10px', width: '300px' }}
bordered
dataSource = { this.props.list }
renderItem = { (item, index) => (
<List.Item
onClick = { (index) => {
this.props.handleItemDelete(index)
} }
>
{ item }
</List.Item>
)}
/>
</div>
)
}
}
export default TodoListUI
我们可以用无状态组件来写:
import React from 'react'
import { Input, Button, List } from 'antd'
// 无状态组件
const TodoListUI = props => {
return (
<div style = {{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value = { props.inputValue }
placeholder="Basic usage"
style = {{ width: '300px', marginRight: '10px'}}
onChange = { props.handleInputChange }
/>
<Button
type="primary"
onClick = { props.handleBtnClick }
>提交</Button>
</div>
<List
style = {{ marginTop: '10px', width: '300px' }}
bordered
dataSource = { props.list }
renderItem = { (item, index) => (
<List.Item
onClick = { (index) => {
props.handleItemDelete(index)
} }
>
{ item }
</List.Item>
)}
/>
</div>
)
}
无状态组件的性能比较高;因为他就是一个函数,只有render的组件是一个类,有声明周期函数等, 使用无状态组件来提高性能。
评 论:
jsx →