引入方式
- 两种方式
- CDN 引入(顺序不能错)
- 引入 React:https://.../react.x.min.js
- 引入 ReactDOM:...react-dom.x.min.js
- 两种不同的 React 版本区别:cjs 和 umd
-
cjs 全称 CommonJS,是 Node.js 支持的模块规范
-
umd 是统一模块定义,兼容各种模块规范,包括浏览器
-
理论上优先使用 umd,同时支持 Node.js 和浏览器
-
最新的模块规范是使用 import 和 export 关键字
- 使用 React 脚手架
- 安装 create-react-app
yarn global add create-react-app // or npm install create-react-app -g
- 使用命令创建项目
create-react-app 项目名
- 运行命令
yarn start
使用 React
- 创建一个 React 元素
import React from 'react'
import ReactDOM from 'react-dom'
const App = React.createElement('div', null, n)
// app 是一个 React 元素
- createElement 的返回值 element 可以代表一个 div;但是, element 并不是真正的 div(DOM 对象);我们一般称 element 为
虚拟DOM
对象。 - 创建一个 React 组件
...
const App = () =>
React.createElement('div', null, [...])
ReactDOM.render(App(),document.querySelector('#app'))
- 以上箭头函数作为一个可以返回 element 的函数,也可以代表一个 div,这就是 React 的函数组件。这个函数可以
执行多次
,每次得到最新的虚拟 div;React 会对比每个虚拟 div,找出不同,局部更新视图;这种找不同的算法叫做DOM Diff
JSX
- Vue 有 vue-loader
- .vue 文件里写
<template>、<script>、<style>
标签时,通过 vue-loader 变成一个构造选项
- .vue 文件里写
- React 有 JSX
- React 也有自己的编译工具 JSX-loader,但是已经被 babel-loader 取代了,webpack 内置了 babel-loader,所以使用 React 进行开发时,不需要再手动配置。
- 通过 JSX,我们可以更简洁的使用 React
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
return (
<div>App组件</div>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
几个注意点
- 注意 className
<div className='red'>App组件</div>
会被转译为React.createElement('div',{className:'red'},'n')
- 所以类名不能直接使用 class,而是要使用 className
- 插入变量
- 标签里面的所有 JS 代码都要用 { } 包起来
- 如果需要使用变量 n,那么就用 { } 把 n 包起来
- 如果需要使用对象,那么久要用 { } 把对象包起来,比如:
{{name:'zhangsan'}}
- 习惯 return 后面加 ( )
- 如果 return 后面换行后,返回值会变成 undefined
React 使用判断语句
import React from 'react'
import ReactDOM from 'react-dom'
const Component = () => {
return (
n % 2 === 0 ? <div>n 为偶数</div> : <div>n 为奇数</div>
)
}
// or
const Component = () => {
return (
<div>
{n % 2 === 0 ?<div>n 为偶数</div> : <div>n 为奇数</div>}
</div>
)
}
// or
const Component = () => {
const inner = n % 2 === 0 ?<div>n 为偶数</div> : <div>n 为奇数</div>
return (
<div>
{inner}
</div>
)
}
// or
const Component = () => {
let inner
if(n % 2 !== 0){
inner = <div>n 为奇数</div>
} else {
inner = <div>n 为偶数</div>
}
return (
<div>
{inner}
</div>
)
}
- 以上,React 不同于 Vue, 它的代码非常随意,只要符合规范,随便写,就是在写 JS 而已。
React 使用循环语句
...
const App = (props) => {
return props.number.map((item, index)=>{
return (
<div>
{<div>下标为{index}的值为{item}</div>}
</div>
)
})
}
// or
const App = (props) => {
const array = []
for (let i = 0; i < props.number.length; i++) {
array.push(<div>下标为{i}的值为{props.number[i]}</div>)
}
return <div>{array}</div>
}
// React 会将外部数据放在第一个参数里
ReactDOM.render(<App numbers={[1, 2, 3]}/>, document.querySelector('#app'))
函数组件和类组件
- 函数组件
- 通过参数获取外部数据,React 会将外部数据放在第一个参数里
function Welcome(props) {
return <div>Hello, {props.name},welcome to React</div>
}
// 使用
<Welcome name="zhangsan" />
- 函数组件实现点击 + 1
- 函数组件的数据初始化使用 React.useState(),其参数为数据的初始值
- React.useState 返回一个数组,数组的第一项用来读,第二项用来写
- setN 不会改变初始数据 n,而是会产生一个新的数据
import React from 'react'
import ReactDOM from 'react-dom'
function Add(){
const [n, setN] = React.setState(0)
return (
<div>
{n}
<button onClick={()=>setN(n + 1)}> + 1</button>
</div>
)
}
ReactDOM.render(<Add />, document.getElementById('app'))
- 类组件
- 通过 this.props 获取外部数据
class Welcome extends React.Component {
render() {
return <div>Hello, {this.props.name},welcome to React</div>
}
}
// 使用
<Welcome name="zhangsan" />
- 类组件实现点击 + 1
- 类组件的数据初始化写在 constructor 函数里
- 读: this.state.n
- 写: this.setState({n:this.state.n + 1}) 或者 this.setState((state, props)=> {return {n:state.n + 1}})
- 要生成一个新的对象,传给 setState
- setState 是异步的
import React from 'react'
import ReactDOM from 'react-dom'
class Add extends React.Component {
constructor(){
super()
this.state = {
n: 0
}
}
render(){
return (
<div>
{this.state.n}
<button onClick={()=>this.setState({n:this.state.n + 1})}> + 1</button>
</div>
)
}
}
ReactDOM.render(<Add />, document.getElementById('app'))
- 多个数据的情况
- 如果类组件的数据有多个时,m 或者 n 其中一个变化,React 会自动沿用其他数据之前的旧值。(只有一层,如果修改的是对象内部的数据,也需要手动沿用)
... class App extends React.Component{ constructor(){ super() this.state = { n: 0, m: 0 } } addN(){ this.setState({n:this.state.n + 1}) } addM(){ this.setState({m:this.state.m + 1}) } render(){ return ( <div> {this.state.n} <button onClick={()=>this.addN()}>n + 1</button> {this.state.m} <button onClick={()=>this.addM()}>m + 1</button> </div> ) } } ...
- 如果函数组件数据有多个时,m 或者 n 其中一个变化,React 不会自动沿用其他数据之前的旧值,则需要手动沿用。
... const App = () => { const [state, setState] = React.useState({ n: 0, m: 0 }) return ( <div> {n} <button onClick={()=>setState({...state, n:state.n + 1})}>n + 1</button> {m} <button onClick={()=>setState({...state, m:state.m + 1})}>n + 1</button> </div> ) } ...
React 事件绑定
类组件的事件绑定
...
add(){
this.setState(state => {
return {n:state.n + 1}
})
}
render(){
return (
// 最安全的写法
<div>
<button onClick={() => this.add()}>n + 1</button>
</div>
// 以下写法会使 this 变成 window。代码执行时,React 会调用 button.onClick.call(null, event),add 函数里的 this 就会变成 window
<div>
<button onClick={this.add}>n + 1</button>
</div>
// 使用 bind 来指定 this 是正确的,它返回了一个绑定当前 this 的新函数,但是略复杂
<div>
<button onClick={this.add.bind(this)}>n + 1</button>
</div>
// 或者将函数起个名字: this._add = () => this.add()
<div>
<button onClick={this._add}>n + 1</button>
</div>
)
}
...
- 又要给函数起一个新的名字还是很麻烦,我们还有更好的方法
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
constructor() {
super()
this.add = () => this.setState(state=>{
return {n: state.n + 1}
})
}
/* 或者直接写在外面
add = () => this.setState(state => {
return {n: state.n + 1}
}) */
render(){
return (
<div>
<button onClick={this.add}>n + 1</button>
</div>
)
}
}
Props 外部数据
Props 的作用
- 接收外部数据
- 只能读不能写
- 外部数据由父组件传递
- 接收外部函数
- 在恰当的时机,调用该函数
- 该函数一般是父组件的函数
类组件 props
- 传入 props 给 B 组件
- 外部数据被包装成一个对象:
{name:'zhangsan',onClick:..., children:'hello'}
- 此处的 onClick 是一个回调
- 外部数据被包装成一个对象:
...
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'zhangsan'}
}
onClick = () => {}
// 组件的外部数据一般来自于父组件的内部数据
render(){
return <B name={this.state.name}
onClick={this.onClick}>hello</B>
}
}
...
- B 组件初始化外部数据
- 这样做了之后, this.props 就是
外部数据对象的地址
- 这样做了之后, this.props 就是
...
class B extends React.Component {
// 如果 constructor 没有其他数据,可以忽略不写
constructor(props) {
super(props)
}
render(){}
}
...
- 不要在 B 组件内部修改 props
State 内部数据
类组件
初始化 state
...
class B extends React.Component {
constructor(props) {
super(props)
this.state = {
user: {name: 'zhangsan', age: 18}
}
}
render(){...}
}
...
读取 state
this.state.xx.yy
修改 state
- 直接传入一个新的 state:
this.setState(newState,fn)
- setState 不会立刻改变 this.state,会在当前代码执行完毕后,再去更新 this.state,从而触发 UI 更新
- 使用函数:
this.setState((state,props)=>newState,fn)
- fn 会在写入成功后执行
- setState 会自动将新 state 与旧 state 进行一级合并
生命周期
constructor()
构造、初始化
- 在这里初始化 state、props
shouldComponentUpdate(newProps, newState)
是否更新
- return false 阻止更新
- 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活的设置返回值,以避免不必要的更新
- React 内置了此功能,在创建组件的时候,使用
PureComponent
,PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
...
class B extends React.PureComponent {}
...
render()
渲染
- 创建虚拟 DOM
- 用于展示视图
return (<div>...</div>)
- 如果有多个根元素,要用
<React.Fragment>
包起,可以缩写成<></>
... render(){ return ( <> <div>...</div> <div>...</div> </> ) } ...
- render 里面可以写 if..else、三元表达式
...
render(){
let message
if(this.state.n % 2 === 0) {
message = <div>偶数</div>
} else {
message = <div>奇数</div>
}
return (
<>
{message}
<button onClick={this.onClick}>n + 1</button>
</>
)
}
...
- render 里面不能直接写 for 循环(无返回值),需要用数组
...
render(){
let result = []
for(let i = 0; i < this.state.array.length; i++){
result.push(this.state.array[i])
}
return result
}
...
- render 里面可以写 map 循环
...
render(){
return this.state.array.map(n => <span key={n}>{n}</span>)
}
...
componentDidMount()
组件挂载完成
- 组件已经出现在页面
- 用于在元素插入页面后执行代码,这些代码一般依赖 DOM
// 展示一个div元素的宽度
...
class App extends React.PureComponent {
constructor(props){
super(props)
this.state = {
width: undefined
}
}
componentDidMount(){
const div = document.getElementById('xx')
const {width} = div.getBoundingClientRect() // 返回元素的大小及其基于视口的位置
this.setState({width:width})
}
render(){
return (
<div id="xx">{this.state.width}</div>
)
}
}
...
- React 提供了 React.creatRef(),以更方便的选取 DOM 元素
...
class App extends React.PureComponent {
constructor(props){
super(props)
this.state = {
width: undefined
}
// 1. 创建一个 ref
this.difRef = React.creatRef()
}
componentDidMount(){
// 3. 获取
const div = this.divRef.current
const {width} = div.getBoundingClientRect()
this.setState({width})
}
render(){
return (
// 2. 挂载
<div ref={this.divRef}>{this.state.width}</div>
)
}
}
- 此处可以发起
加载数据
的 AJAX 请求(官方推荐) - 首次渲染会执行此钩子
componentDidUpdate(preProps,prevState,snapshot)
组件更新完成
- 组件已经更新
- 在视图更新后执行代码
- 此处也可以发起 AJAX 请求,用于
更新数据
- 首次渲染
不会
执行此钩子 - 在此处 setState 可能会引起无限循环,除非放在 if 里
- 若
shouldComponentUpdate(newProps, newState)
返回false
,则不会触发此钩子
componentWillUnmount()
组件将要卸载前
- 组件将要被移除页面然后被销毁时执行代码
- unmount 过的组件不会再次 mount
- 如果在
componentDidMount
里面监听了window. scroll
,那么就要在componentWillUnmount
里面取消监听 - 如果在
componentDidMount
里面创建了Timer定时器
,那么就要在componentWillUnmount
里面取消定时器 - 如果在
componentDidMount
里面创建了AJAX 请求
,那么就要在componentWillUnmount
里面取消请求
- 如果在
函数组件的生命周期
函数组件要比 class 组件简洁的多,但是函数组件没有 state 和声明周期
没有 State
- React v16.8.0 推出了 Hooks API,其中的一个 API 叫做 useState 可以解决
没有声明周期
- Hooks API 中的 useEffect 可以模拟生命周期
- useEffect 的第二个参数接受一个数组,当数组中的任意数据变化时,第一个参数回调里的代码就会执行,如果数组为空,则只会在第一次渲染时执行一次,如果直接不写,那任何一个数据变化都会执行前面的回调,如果 useEffect 返回了一个函数,那么这个函数会在组件即将销毁时执行。
- 自定义 Hook,解决第一次渲染执行的问题,只在数据变化时执行
import React,{useState,useEffect} from 'react' import ReactDOM from 'react-dom' const useUpdate = (fn, dep) => { const [count, setCount] = useState(0) useEffect(()=>{ setCount(x => x+1) }, [dep]) useEffect(()=> { if(count > 1){ fn() } }, [count, fn]) } const App = ()=> { const [n, setN] = useState(0) const onClick = () => { setN(n + 1) } useUpdate(()=>{ console.log('n 变了') }, n) return ( <div> {n} <button onClick={onClick}> + 1</button> </div> ) } ReactDOM.render(<App />,document.getElementById('app'))
- 模拟 componentDidMount
useEffect(()=>{ console.log('第一次渲染') }, [])
- 模拟 componentDidUpdate
useEffect(()=>{ console.log('任意属性变更') })
useEffect(()=>{ console.log('n 变了') }, [n])
- 模拟 componentWillUnmount
useEffect(()=>{
console.log('第一次渲染')
return ()=>{
console.log('组件即将消亡')
}
})
- constructor
- 函数组件执行的时候,就相当于 constructor
- shouldComponentUpdate
React.memo
和useMemo
可以解决
- render
- 函数组件的返回值就是 render 的返回值
React 和 Vue 的区别
相同点
- 都是对视图的封装,React 是用类和函数表示一个组件,而 Vue 是通过构造选项构造一个组件
- 都提供了 createElement 的 XML 简写,React 提供的是 JSX 语法,而 Vue 提供的是模板写法(语法巨多)
不同点
- React 是把 HTML 放在 JS 里面写(HTML in JS),而 Vue 是把 JS 放在 HTML 里面写(JS in HTML)