React是一个由Facebook开发和主要维护的,JavaScript库,它不是框架,因为它只关注视图层
创建项目
这里介绍两种方式
第一种,安装官方提供的脚手架工具
npm install -g create-react-app
create-react-app todolist
第二种,使用npm自带的npx
npx create-react-app todolist
项目目录
|____public # 公共资源文件
| |____favicon.ico
| |____index.html
| |____logo512.png
| |____manifest.json # 桌面图标
| |____robots.txt
| |____logo192.png
|____package.json
|____src #项目源码
| |____reportWebVitals.js
| |____App.css
| |____index.js # 项目入口
| |____index.css
| |____App.test.js
| |____setupTests.js
| |____logo.svg
| |____App.js
组件
函数组件
function App() {
return <div>Hello World</div>
}
export default App
类组件
import React, { Component } from 'react'
class App extends Component {
render() {
return <div>Hello World</div>
}
}
export default App
在函数中写HTML标签时JSX语法,为了能够正常编译,必须引入React
使用自定义组件
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
在使用自定义组件时首字母必须大写,正确写法<App/>
,错误写法<app/>
Fragment
JSX语法规定组件最外层必须有一个元素包裹,如果不希望这个元素被渲染出来,可以使用<Fragment>
占位符
import React, { Component, Fragment } from 'react'
class TodoList extends Component {
render() {
return (
<Fragment>
<div>
<input type="text" />
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>学React</li>
</ul>
</Fragment>
)
}
}
export default TodoList
React响应式和事件绑定
数据绑定
import React, { Component, Fragment } from 'react'
class TodoList extends Component {
constructor(props) {
super(props)
// state 存储组件的状态
this.state = {
inputValue: 'hello',
list: []
}
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} type="text" />
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>学react</li>
</ul>
</Fragment>
)
}
}
export default TodoList
React通过state
中存储组件的状态。将属性的值绑定到变量的语法为属性名={this.state.变量名}
事件绑定
import React, { Component, Fragment } from 'react'
class TodoList extends Component {
constructor(props) {
super(props)
// state 存储组件的状态
this.state = {
inputValue: 'hello',
list: []
}
}
render() {
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>学react</li>
</ul>
</Fragment>
)
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
}
export default TodoList
React中绑定事件的语法为事件名={this.事件处理函数.bind(this)}
,React改变state
中的值必须通过this.setState({ key: newVal })
循环
<ul>
{this.state.list.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
React循环需要为循环出来的每一项增加一个key值,建议不要用index作为key
对数组的修改
React中直接修改数组可以生效,但不建议这样做,因为这会对React性能造成影响,应该先拷贝一份出来再通过setState
进行修改
handleItemDelete(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list
})
}
注释
单行注释
{
// 单行注释
}
多行注释
{/* 多行注释 */}
className
React中用className
代替了HTML中的class
属性
<input className="myInput" type="text" />
插入HTML
React进行数据渲染时会对数据进行转义,如果需要展示HTML文本,可以使用dangerouslySetInnerHTML
,但值得注意的是,这种方式存在xss攻击的可能
<ul>
{this.state.list.map((item, index) => {
return <li key={index} onClick={this.handleItemDelete.bind(this, index)} dangerouslySetInnerHTML={{ __html: item }}></li>
})}
</ul>
htmlFor
为了不和JSX语法中的for循环冲突,label
中的for用htmlFor
进行替换
<label htmlFor="name"></label>
<input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
组件传值
父组件向子组件传递数据
parent
<ul>
{this.state.list.map((item, index) => {
return (
<div key={index}>
<TodoItem content={item} />
</div>
)
})}
</ul>
父组件向子组件传值语法属性名={变量}
child
import React, { Component } from 'react'
class TodoItem extends Component {
render() {
return <div>{this.props.content}</div>
}
}
export default TodoItem
子组件获取父组件传递的值语法this.props.属性名
子组件向父组件传递数据
React可以通过调用父组件传递过来的方法来修改父组件中的参数
parent
import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
constructor(props) {
super(props)
// state 存储组件的状态
this.state = {
inputValue: '',
list: []
}
}
render() {
return (
<Fragment>
<div>
<label htmlFor="name">姓名</label>
<input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
<ul>
{this.state.list.map((item, index) => {
return (
<div key={index}>
<TodoItem content={item} index={index} deleteItem={this.handleItemDelete.bind(this)} />
</div>
)
})}
</ul>
</Fragment>
)
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
// 删除TODO
handleItemDelete(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({
list
})
}
// 新增TODO
handleBtnClick(e) {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})
}
}
export default TodoList
传递的方法的同时需要通过bind修改this指向,否则在子组件使用时会出现xxx not a function
的错误
child
import React, { Component } from 'react'
class TodoItem extends Component {
constructor(props) {
super(props)
// 将 this 绑定放到构造函数中执行可以提升代码效率
this.handleClick = this.handleClick.bind(this)
}
render() {
return <div onClick={this.handleClick}>{this.props.content}</div>
}
handleClick() {
// 调用父组件传递的方法修改父组件参数
this.props.deleteItem(this.props.index)
}
}
export default TodoItem
React代码优化
- 将函数this绑定放到构造函数中
import React, { Component } from 'react'
class TodoItem extends Component {
constructor(props) {
super(props)
// 将 this 绑定放到构造函数中执行可以提升代码效率
this.handleClick = this.handleClick.bind(this)
}
}
export default TodoItem
- 使用es6的解构赋值简化代码
import React, { Component } from 'react'
class TodoItem extends Component {
handleClick() {
const { deleteItem, index } = this.props
// 调用父组件传递的方法修改父组件参数
deleteItem(index)
}
}
export default TodoItem
- setState传递一个方法
import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
handleInputChange(e) {
this.setState(() => {
return {
inputValue: e.target.value
}
})
}
}
export default TodoList
- 使用prevState
import React, { Component, Fragment } from 'react'
import TodoItem from './TodoItem'
class TodoList extends Component {
constructor(props) {
super(props)
// state 存储组件的状态
this.state = {
inputValue: '',
list: []
}
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
this.getTodoItem = this.getTodoItem.bind(this)
}
render() {
return (
<Fragment>
<div>
<label htmlFor="name">姓名</label>
<input id="name" className="myInput" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} type="text" />
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>{this.getTodoItem()}</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return <TodoItem content={item} index={index} deleteItem={this.handleItemDelete} />
})
}
handleInputChange(e) {
this.setState(() => {
return {
inputValue: e.target.value
}
})
}
// 删除TODO
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {
list
}
})
}
// 新增TODO
handleBtnClick(e) {
// prevState 是修改前的 state,这是一种更严谨的写法
this.setState((prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}
})
}
}
export default TodoList
- shouldComponentUpdate
child component
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.content !== this.props.content) {
return true
} else {
return false
}
}
一般情况下,父组件调用render后子组件也会调用render,但有时子组件并不需要更新,这是可以通过shouldComponentUpdate
返回false来避免重新渲染子组件
React特性
- 声明式开发
- 可以与其他框架并存
- 组件化
- 单向数据流
防止子组件意外修改父组件的值造成调试困难
视图层框架
函数式编程
React开发工具的安装和使用
Chrome插件 React Developer Tools
可以对数据进行监听
参数校验和参数默认值
propTypes设置参数校验规则,defaultProps设置参数默认值
TodoItem.propTypes = {
test: PropTypes.string.isRequired,
content: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
deleteItem: PropTypes.func,
index: PropTypes.number
}
TodoItem.defaultProps = {
test: '请输入todo内容'
}
props、state与render的关系
当组state或者
props发生改变时,
render`函数就会重新执行
当父组件的render重新执行时,子组件的render也会重新执行
虚拟DOM
state 数据
JSX 模板
数据+模板结合,生成真实DOM并进行显示
生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)
state 发生变化时生成新的虚拟DOM(极大提升了性能)
比较原始虚拟DOM和新的虚拟DOM的区别(极大提升了性能)
直接操作DOM,改变DOM中内容
深入理解虚拟DOM
以下两种语法是等价的
JSX语法
render() {
// return <div>{this.props.content}</div>
return (
<div>
<span>JSX</span>
</div>
)
}
原生JS语法
render() {
return React.createElement('div', {}, React.createElement('span', {}, 'JS'))
}
Diff算法
React的虚拟DOM进行同层比对,当某一结点不同时,其子孙结点都会被替换。优点是算法简单,缺点是可能造成性能问题。
key可以大大提升Diff算法的效率:key可以建立新虚拟DOM的结点和原虚拟DOM结点的对应关系,方便进行比对。如果使用index作为key,由于index是不稳定的,这个时候key就不生效了。
ref
React提供的获取DOM结点的方法
<input
ref={(input) => (this.input = input)}
/>
这条语句的作用是将input挂载到this.input
上
使用ref获取DOM元素
handleInputChange() {
this.setState(() => {
return {
inputValue: this.input.value
}
})
}
使用ref需要注意的问题
handleBtnClick(e) {
// prevState 是修改前的 state,这是一种更严谨的写法
this.setState((prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}
})
console.log(this.ul.querySelectorAll('li').length)
}
由于修改state是异步的,打印语句会先执行,所以length
总比真实情况少1
解决方案
将代码放到setState
的回调函数中,可以确保获取的修改后的state
handleBtnClick(e) {
// prevState 是修改前的 state,这是一种更严谨的写法
this.setState(
(prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}
},
() => {
console.log(this.ul.querySelectorAll('li').length)
}
)
}
React生命周期函数
生命周期函数指在某一个时刻组件会自动调用执行的函数
钩子函数 | 触发行为 | 在此阶段可以做的事情 |
---|---|---|
constructor | 数据初始化时 | |
componentWillMount | 在组件即将被挂载到页面时刻执行 | |
render | 在组件渲染时执行 | |
componentDidMount | 在组件被挂载到页面后执行 | 发送ajax请求 |
componentWillReceiveProps | 从父组件接收改变后的props时执行(父组件重新执行render之后) | |
shouldComponentUpdate | 组件更新前触发,返回true更新,返回false不更新,之后的钩子函数都不执行 | |
componentWillUpdate | 组件更新前,shouldComponentUpdate执行后触发,必须上一阶段返回true才会执行 | |
componentDidUpdate | 组件更新后执行 | |
componentWillUnmount | 组件即将从页面移除时执行 |
子组件移除过程 shouldComponentUpdate->componentWillUpdate->componentWillUnmount(子组件)->componentDidUpdate
Charles实现本地数据mock
由于Charles不再支持捕获localhost的请求,所以必须让项目支持用域名访问,在React项目中可以进行以下修改
package.json
"scripts": {
"start": "set PORT=3000 HOST=localhost.charlesproxy.com && react-scripts start"
},
Charles配置
打开Charles,Tools->Map Local->Enable Map Local
开启local map
配置map
发送ajax请求
async componentDidMount() {
const res = await axios.get('/api/todolist')
this.setState(() => {
return {
list: [...res.data]
}
})
}
React动画
css
.show {
opacity: 1;
transition: all 1s ease-in;
}
.hide {
opacity: 0;
transition: all 1s ease-in;
}
jsx
根据show
添加类名
<input
ref={(input) => (this.input = input)}
id="name"
className={this.state.show ? 'show' : 'hide'}
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
type="text"
/>
<button onClick={this.handleBtnClick}>提交</button>
点击按钮时让show
在true
和false
之间切换
handleBtnClick(e) {
// prevState 是修改前的 state,这是一种更严谨的写法
this.setState(
(prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: '',
show: !prevState.show
}
},
() => {
console.log(this.ul.querySelectorAll('li').length)
}
)
}
react-transition-group实现动画
文档地址https://reactcommunity.org/react-transition-group/
安装依赖
yarn add react-transition-group
导入依赖
import { CSSTransition } from 'react-transition-group'
CSSTransition使用
<Fragment>
<CSSTransition
unmountOnExit
onEntered={(el) => {
el.style.color = 'red'
}}
classNames="fade"
in={this.state.show}
timeout={1000}
>
<div>React Animation</div>
</CSSTransition>
<button onClick={this.handleBtnClick}>开始动画</button>
</Fragment>
in={this.state.show} 根据
show
的值判断当前动画的状态,是处于出场动画还是入场动画timeout={1000} 设置动画执行时间,单位毫秒
classNames="fade" 动画名称
unmountOnExit 在退出动画时移除元素
appear={true} 在元素第一次出现时显示动画
动画过渡期间的钩子函数onEnter、onEntering 、onEntered(入场动画结束后)、onExit、onExiting、onExited
动画过渡期间的类名
- fade-enter 入场动画前
- fade-enter-active 入场动画中
- fade-enter-done 入场动画后
- fade-exit 出场动画前
- fade-exit-active 出场动画中
- fade-exit-done 出场动画后
TransitionGroup使用
TransitionGroup是一个组件,用于列表项动画。以下是列表新增元素动画的例子
js
render() {
return (
<Fragment>
<TransitionGroup>
{this.state.list.map((item, index) => {
return (
<CSSTransition
unmountOnExit
onEntered={(el) => {
el.style.color = 'blue'
}}
classNames="fade"
in={this.state.show}
timeout={1000}
key={index}
>
<div>{item}</div>
</CSSTransition>
)
})}
</TransitionGroup>
<button onClick={this.handleBtnClick}>开始动画</button>
</Fragment>
)
}
css
.fade-enter {
opacity: 0;
}
.fade-enter-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;
}