前言
vue
和 react
都是 js 框架,均是 mvvm
框架和 SPA 单页应用,通过数据更新驱动试图的更新
二者在应用场景方面是有一定区别的,vue 源码设计之初其目的就是让用户更好的上手,封装的很彻底。因此 vue 非常适合移动端、中小型 pc 端项目的开发,目前很多大公司都是 react 开发
目前 react 最新版本已经达到 18 了,react 更新迭代很快,基本上一年一更,react 是由 Facebook 公司开源的,生态非常完善,react 还有一套源码 react-native
用于移动端开发
其实 vue 的诞生就是借鉴了 react 的设计理念,本文将从有一定
vue
基础的视角去学习react
正文
先用脚手架创建一个 react 项目,此前用 vue 项目时用得脚手架有 vite,其实 vite 也可以创建 react,甚至可以创建 angular,vue 的脚手架是 Vue-cli
官方文档介绍中如何创建一个 react 框架,它用的是 next
next
是 react 的一个框架, next 将 react 封装成为一个全栈式框架,nuxt
是 vue 的框架,nest
是 node 框架
记得用 vue-cli 还需要在电脑上安装东西,而 react 不用
npm
是 node 中的包管理工具,而 npx
也是 node 管理工具,一般临时用 npx
这里我们不用 next 来创建 react 项目,那个给你弄成全栈式了,这里没必要,还有个 Create React App 脚手架
react 脚手架无需我们安装,react 官方直接将这个脚手架上传到了 node 库中,npx 安装是临时安装的,这个项目一旦创建完成,脚手架的源码是会自动删除的
安装很慢可以考虑 node 源不行,换源方法此前提过初识node | 模块化 | yrm换源 | nvm切换node - 掘金 (juejin.cn)
还是不行可以直接把 creat-react-app 脚手架全局下载下来
npm i -g creat-react-app
还是不行就用 vite
npm create vite@latest react-basic -- --template react
vite 需要自行安装依赖,这里我就用 vite 来创建
项目目录
大致解释下生成的目录文件
package.json
用 vue 的都知道,vue 这里的 dependencies
只有一个,但是 react 有两个,react 官方将 react 源码拆成了两部分,一个是 react
源码,还有个是 react-dom
,其实 vue 也有个 dom 是 compiler-dom
, react 将其拆分了出来, react-dom
源码就是用于将虚拟 dom 转换成真实 dom
index.html
跟 vue 区别不大,vue 中这个 div 的 id 为 app,这里是 root
src/assets/react.svg
svg
图片是用代码描述的,性能更好
main.jsx
类似于 vue 的 main.js,create-react-app 创建的都是 js 后缀,而 vite 都是 jsx
,其实二者没有区别
react 中所有的文件都是 js 文件,不像 vue 都是 vue 后缀
看这个 main.jsx 的最后代码,引入两份 react 代码,通过 reactDom
来渲染来自 App.jsx 中的 root,并用的 react 的严格模式,render
就相当于 vue 中的 mount
挂载
App.jsx
App.jsx
其实就是 vue 中的 App.vue,根组件。我们看这个代码,跟原生 js 简直一模一样,没有 vue 中的 template 模板语法, react 就是原生 js 开发,创建一个函数,抛出一个函数。 react17
或者更早的版本是类组件写法
package.json
同样,这个是项目说明书,我们可以看到项目如何启动:npm run dev
为什么 vite 创建的 js 是 jsx
其实 react 脚手架用的 create react app 更多,不过 vite 创建的 react 项目默认是 jsx 后缀,本质上和 js 后缀没有区别。
但是非要论区别,jsx 有点像是 vue 的 template,jsx 等同于 js 和 html 或者 xml, jsx 语法就是在 js 中写 html,其优势在于降低我们的学习成本, jsx 是 js 的拓展语法,浏览器同样读不懂这个语法, react 源码可以帮你读懂,其实 vue3 也可以读懂这个语法
其实这个语法和 jsp 有一点相似, jsp 是在 js 中写 java,而 jsx 是在 js 中写 html
当然 jsx 和 js 的区别还有一个,就是 jsx 可以快捷方式写 html
语法
react 学起来会比 vue 快,因为 react 贴近原生 js 开发
组件式开发
既然也是组件开发,那么就存在组件之间的通讯,待会儿再看
比如我把 App.jsx
改成如下代码,这就是 jsx 语法
function App () {
return (
<div className="app">
<h2>hello React</h2>
</div>
)
}
export default App
react 中的类名是 className
而非 class
js需要写在{ }中
这里简单写一个 ul,如果是 vue 就是 v-for,我们看 react
const songs = [
{ id: 1, name: '爷爷泡的茶' },
{ id: 2, name: '发如雪' },
{ id: 3, name: 'Things you said' }
]
function App () {
return (
<div className="app">
<h2>hello React</h2>
<ul>
{
songs.map(item => {
return <li>{item.name}</li>
})
}
</ul>
</div>
)
}
export default App
react 还是用的 { }
,目前这样写是会报一个错的
报了一个没有唯一 key
的错误的,熟悉吧,跟 vue 很像,不过 vue 不会报错,均是涉及到 diff 算法的内容
给 li
加上唯一 key
return <li key={item.id}>{item.name}</li>
这里遍历用的 map
,如果用 forEach
是没有效果,我们需要有一个返回值给到外层的 { }
jsx 中不能存在 if 语句
假设写一个开关变量,为 true
展示某个内容,为 false
展示别的内容
这样写是不行的,报错
jsx 语法中是不能出现 if
语句,一般我们会去写三元运算
<h3>{flag ? 'react' : 'vue'}</h3>
逻辑与运算符
我们可以写一个开关变量控制一个标签的出现,就是用的与 &&
运算符,很优雅
{ flag && <a href="#">Dolphin</a> }
用三元就是下面这样
{ flag ? <a href="#">Dolphin</a> : null }
行内样式
先看下行内样式如何写的
<h2 style={{color: 'red'}}>红色字体</h2>
这里虽然也是 {{ }}
,很像是 vue 挖坑写法,但是其实不是,外层 { }
是用来写 js 的,里面的 { }
是样式, red 一定要打引号,否则当成变量处理
当然,你也可以把样式写到一个对象中,这个对象拿出去写
const styleObj = {
color: 'blue'
}
function App () {
return (
<div className="app">
<h2 style={styleObj}>蓝色字体</h2>
</div>
)
}
export default App
样式写到css中
App.jsx
import './App.css'
function App () {
return (
<div className="app">
<h2 className="green">绿色字体</h2>
</div>
)
}
export default App
App.css
.green{
color: green;
}
这种写法样式就不需要打引号了,一个组件同级目录下对应一个 css
文件,记得引入
动态绑定类名
import './App.css'
const showGreen = true
function App () {
return (
<div className="app">
<h2 className={showGreen ? 'green' : ''}>绿色字体</h2>
</div>
)
}
export default App
依旧是 { }
写 js
jsx 表达式只能一个父元素
像下面这种写法是不允许的
function App () {
return (
<div className="app"></div>
<div className="box"></div>
)
}
export default App
return 里面不能有两个节点,若非要两个就用一个空的 div
包裹一下,其实还有个更优雅的写法,直接写个空
的容器,如下
function App () {
return (
<>
<div className="app"></div>
<div className="box"></div>
</>
)
}
export default App
组件
组件名一定要大写,另外,在 react 中,一个函数就是一个组件
function HellowReact () {
return <div>这是一个函数组件</div>
}
function App () {
return (
<div className="app">
<HellowReact />
</div>
)
}
export default App
当你写的 html 只有一个节点时,就可以 return 时不加括号
另外,这里还可以换个写法,函数放到 { }
中调用,返回给 { }
function HellowReact () {
return <div>这是一个函数组件</div>
}
function App () {
return (
<div className="app">
{
HellowReact()
}
</div>
)
}
export default App
18 版本之前的是类编程,而不是现在这样写函数,类也是函数,这种写法复杂点,如下
import React from 'react'
class HelloVue extends React.Component {
render() {
return <h3>这是一个类组件</h3>
}
}
function App () {
return (
<div className="app">
<HellowReact />
<HelloVue />
</div>
)
}
export default App
类中写 return 之前需要用上 render
,而这个 render 又需要 import
导入 ,并且从 Component
中继承过来
绑定事件
在 vue 中想要绑定一个事件是 v-on ,而 react 则是用原生 js 写法
原生 js 写法有两种,一种是获取 dom 然后 addEventListener
监听;另一种是比如 button 上写一个 onclick
,通过 on 来绑定
react 是采用后者写法,不过与原生略有不同,on 后面的单词采用驼峰写法
function HellowReact () {
const handler = (e) => {
console.log(e, '按钮被点击');
}
return <div>
<p>这是一个函数组件</p>
<button onClick={handler}>click me</button>
</div>
}
外部组件
方才讲的组件引入都是内部,这里我们看看外部如何使用
快捷方式
对了,react 可以安装插件 ES7 React/Redux/GraphQL/React-Native snippets
这样就可以用快捷方式 rafc
或者 rsc
来迅速创建一个函数组件,类组件的快捷方式是 rcc
也可以用 rfcp
迅速创建一个函数组件,这个有多个类型限制,待会儿再聊
需要用下面这个插件 Reactjs code snippets
我们再创建一个组件 src/components/ExitComponent.jsx
const ExitComponent = () => {
return (
<div>
<p>外部组件</p>
</div>
)
}
export default ExitComponent
我创建一个数组放进去遍历出来
const ExitComponent = () => {
const list = [
{ id: 1, name: 'react' },
{ id: 2, name: 'vue' }
]
const onDel = (id) => {
}
return (
<div>
<p>外部组件</p>
<ul>
{
list.map(item => (
<li key={item.id}>
<span>{item.name}</span>
<button onClick={() => onDel(item.id)}>x</button>
</li>
))
}
</ul>
</div>
)
}
export default ExitComponent
组件中的变量要是希望是全局的就拿到全局去写
其实
( )
就是{ }
加return
另外,onClick
中的函数若是直接写函数进去,默认是会给你调用的,你需要写在一个函数体中,这是因为 react 的 onClick
不是原生 js 的 onclick
,会被读取一遍,因此会被一上来执行一下
同样的效果我们看下类组件会怎样写
import React, { Component } from 'react'
export default class ClassComponent extends Component {
constructor () {
super()
this.list = [
{ id: 1, name: 'react' },
{ id: 2, name: 'vue' }
]
}
onDel (id) { // 相当于写到了原型上
console.log(id);
}
render() {
return (
<div>
<p>外部类组件</p>
<ul>
{
this.list.map(item => (
<li key={item.id}>
<span>{item.name}</span>
<button onClick={() => this.onDel(item.id)}>x</button>
</li>
))
}
</ul>
</div>
)
}
}
类组件中写参数需要写到 constructor
中,继承还需要在其中写 super
关键字
定义函数就在类中定义即可,无需 static
关键字,这样写就是写在类的原型上,拿到这个函数需要用 this
虽然目前 react 最新版本是函数式编程,但是实际开发中很多企业还是会写类编程,因此还是有学习的必要
我们拿到根组件 App.jsx 中使用,需要导入
import ExitComponent from './components/ExitComponent.jsx';
function App () {
return (
<div className="app">
<ExitComponent />
</div>
)
}
export default App
组件的状态
状态就涉及到数据更新带动视图的更新,在 vue 中我们会使用 ref
或者 reactive
,在 react 我们则是使用 setState
比如下面的这个例子,我希望点击按钮实现 ++
的效果,这里用类展示
import React, { Component } from 'react'
export default class Counter extends Component {
state = { // 这样写相当于在constructor中写this.state
count: 0
}
// react中类编程,函数体中this默认会丢失,是undefined,因此改成箭头函数
setCount = () => {
console.log(this);
this.setState({ // setState从Component类中继承过来的
count: this.state.count + 1
})
}
render() {
return (
<div>
<button onClick={this.setCount}>计数器 -- {this.state.count}</button>
</div>
)
}
}
在 class 写法中, constructor
定义的值可以直接拿到最前面去写,并且不需要使用 this,但是用的时候需要用 this,另外 react 中的 class 的 this 在函数中是会丢失的,因此里面的函数需要写成箭头函数,让 this 指向 class,这里的 setState
我们并没有写过,这说明这个 setState
是来自 Component
中继承而来的。
另外组件中的状态一定要命名为 state
demo:受控表单组件
下面简单写一个双向绑定的 input 框,不同于 vue,这里实现需要先将 state 值赋给 input 中的 value 值,然后利用 onChange
事件函数去调用 setState 改变 state 值,这就是 react 中的受控组件
import React, { Component } from 'react'
export default class InputComponent extends Component {
state = {
msg: ''
}
changeHandler = (e) => {
console.log(e.target.value);
this.setState({
msg: e.target.value
})
}
render() {
return (
<div>
<input type="text" value={this.state.msg} onChange={this.changeHandler} />
</div>
)
}
}
demo:非受控组件
import React, { Component, createRef } from 'react'
export default class InputComponent2 extends Component {
msgRef = createRef() // 创建一个用于存放dom的容器
changeHandler = () => {
console.log(this.msgRef.current.value);
}
render() {
return (
<div>
<input type="text" ref={this.msgRef} />
<button onClick={this.changeHandler}>提交</button>
</div>
)
}
}
非受控组件写法用的是 Ref,这点很像是 vue,都是用 ref 去拿到 dom 结构,然后 this.msgRef.current.value
就是输入框的值
区别
非受控组件直接拿到 dom 结构,受控组件用的是 react 中的 state,一句话解释就是
受控组件 input 框自己的状态被 react 组件状态所控制,非受控组件反之
组件通信
父子传参
类组件写法
父组件
import React, { Component } from 'react'
import CChild from './components/CChild'
export default class CApp extends Component {
state = {
msg: '父组件数据'
}
render() {
return (
<div>
<h2>父组件</h2>
<CChild msg={this.state.msg} />
</div>
)
}
}
子组件
import React, { Component } from 'react'
export default class CChild extends Component {
render() {
return (
<div>
<h4>子组件</h4>
<p>{this.props.msg}</p>
</div>
)
}
}
父组件直接把参数给到子组件,= 左边的数据可以随意取,子组件通过 component
中的 props
来拿到父组件传递过来的参数
函数组件写法
函数式就用不了 this 了,react 将函数中的 this 都给默认改成 undefined
了
子组件
function CChild (props) {
return (
<div>
<h4>子组件</h4>
<p>{props.msg}</p>
</div>
)
}
export default CChild
父组件传递过来的参数就在函数形参 props
中
在 vue 中,官方不建议我们在子组件中修改父组件传递过来的值,不建议,但是也不会报错
react 更狠,父组件传递过来的值是只读的,若修改会报错
传递的数据类型除了 undefined
和 null
不能传递外,其余都可以传,甚至可以传递 jsx
语法
既然 react 中,不能修改来自父组件的值,我们就用父组件的函数来进行修改,这又涉及到子父传参
子父传参
子父传参是逆向的行为,需要一个事件驱动,这里我写一个按钮,点击事件来触发传参
父组件写一个函数传递给子组件,子组件拿到父组件传递过来的函数,给这个函数传递自己的参数
父组件
import React, { Component } from 'react'
import PChild from './components/PChild'
export default class PApp extends Component {
callback = (newMsg) => {
console.log('拿到子组件的数据' + newMsg);
}
render() {
return (
<div>
<h2>父组件</h2>
<PChild cb={this.callback} />
</div>
)
}
}
子组件
import React, { Component } from 'react'
export default class PChild extends Component {
state = {
msg: '子组件的数据'
}
handler = () => {
this.props.cb(this.state.msg)
}
render() {
return (
<div>
<h4>子组件</h4>
<button onClick={this.handler}>传递参数给父组件</button>
</div>
)
}
}
函数式组件是一样的,区别就在于 props
前面加不加 this,若是类组件需要 this,函数式无需 this,props
就是形参
兄弟组件通讯
父作中间人
这里我用函数式编程来写,父组件,子组件A,子组件B,父组件传函数给到 A组件,A组件 传参给 父组件,父组件 把 A组件的参数给到 B组件
子组件A
const BrotherA = (props) => {
const msg = 'A组件数据'
const handler = () => {
props.cb(msg)
}
return (
<div>
<h4 onClick={handler}>子组件A</h4>
</div>
)
}
export default BrotherA
子组件B
const BrotherB = (props) => {
return (
<div>
<h4>子组件B--{props.msg}</h4>
</div>
)
}
export default BrotherB
父组件
import BrotherA from "./components/BrotherA"
import BrotherB from "./components/BrotherB"
const BApp = () => {
let msg = ''
const fn = (newMsg) => {
console.log(newMsg);
msg = newMsg
}
return (
<div>
<h2>父组件</h2>
<BrotherA cb={fn} />
<BrotherB msg={msg} />
</div>
)
}
export default BApp
这里如果你运行,发现实现不了,因为父组件想要改动 msg 需要响应式处理,但是函数式编程没有 this.setState
可供你访问
其实有方法,后面我们再讲,这里先改成 class 写法
在类编程中的任何方式在函数式编程中都有更优雅的方式去写
import React from 'react';
import BrotherA from "./components/BrotherA"
import BrotherB from "./components/BrotherB"
class BApp extends React.Component {
state = {
msg: ''
}
fn = (newMsg) => {
console.log(newMsg);
this.setState({msg: newMsg})
}
render () {
return(
<div>
<h2>父组件</h2>
<BrotherA cb={this.fn} />
<BrotherB msg={this.state.msg} />
</div >
)
}
}
export default BApp
其实 react 的兄弟组件通信只能这样写了,react 中没有 eventBus
,eventBus
在 vue3 中已经被移除了,为了这么点功能没必要引入第三方库。
但是涉及到嵌套比较深层的关系时,比如孙组件传值,就需要用到第三方仓库了
其实还可以用 provide
,inject
来实现组件通信,vue 中可以用这个方法实现祖先组件向后代组件传值,父传子,父传孙……,react 也是这样的
Provider ,Consumer 实现祖先向后代组件传值
react 中的叫法稍微变了一点,父组件提供 Provider ,子组件接受 Consumer 。这里仅展示父向子组件
其实 Provider
和 Consumer
都是一个组件
这两个组件需要从 React.createContext()
中解构出来,祖先组件和后代组件都需要这么干,祖先组件用的 Provider
,后代组件用的 Consumer
,双方都自行解构就不是同一个对象,因此需要另起一个文件 provider
专门写这两个方法
src/provider.js
import React from 'react';
const { Provider, Consumer } = React.createContext()
export {
Provider,
Consumer
}
祖先组件
import React from 'react';
import BrotherB from "./components/BrotherB"
import { Provider } from './provider.js';
class BApp extends React.Component {
state = {
msg: 'hello'
}
render() {
return (
<Provider value={this.state.msg}>
<div>
<h2>父组件</h2>
<BrotherB />
</div >
</Provider>
)
}
}
export default BApp
祖先组件写法上需要用 Provider
把 html
包裹起来,然后把 value
传递给后代组件
后代组件
import React from "react"
import { Consumer } from "../provider"
const BrotherB = (props) => {
return (
<Consumer>
{
value => (
<div>
<h4>子组件B--{value}</h4>
</div>
)
}
</Consumer>
)
}
export default BrotherB
后代组件写法上需要用 Consumer
包裹 html
代码,用上祖先组件传递过来的参数 value
这种方法只能实现从上往下传值,无法实现后代向祖先传值,同 vue 一样
props 参数类型限定
我们看一个情景,父组件给子组件一个数组,让子组件去展示
父组件
import List from "./components/List"
const App = () => {
const colors = [
{ id: 1, name: '红色' },
{ id: 1, name: '蓝色' },
{ id: 1, name: '黄色' }
]
return (
<div>
<h2>hello react</h2>
<List colors={colors} />
</div>
)
}
export default App
子组件
const List = (props) => {
const arr = props.colors
const lists = arr.map((item, index) => <li key={index}>{item.name}</li>)
return (
<div>
<ul>
{lists}
</ul>
</div>
)
}
export default List
假设这个组件是别人写好的,但是人家传递过来的参数可能写错了类型
比如这里父组件里面的这样写colors={100}
报如下错误
看错误说的是子组件的问题
这样子不知情的人很难找到问题所在,因此我们会给 props
做一个错误校验
需要安装依赖prop-types
这个时候你肯定就会觉得,react 官方怎么这种东西自己不考虑好,让第三方来做, vue 的 props 都做了类型校验,确实是这样的,vue 源码是比较多的
安装完依赖后,对子组件 List
进行使用
import PropTypes from "prop-types";
const List = (props) => {
const arr = props.colors
const lists = arr.map((item, index) => <li key={index}>{item.name}</li>)
return (
<div>
<ul>
{lists}
</ul>
</div>
)
}
List.propTypes = { // 为组件添加校验规则
colors: PropTypes.array
}
export default List
这个时候的报错就可以看出问题所在了
“期待的是 array
,却给了我 number
”
因此我们可以用这个去帮我们限定父组件给我们传的类型
若是限定函数,则是如下写法
List.propTypes = { // 为组件添加校验规则
fn: PropTypes.func
}
用的 func
这个关键字
我们还可以对一个参数限定为必传项,直接在后面写 isRequired
List.propTypes = { // 为组件添加校验规则
fn: PropTypes.func.isRequired
}
假设我们希望父组件给子组件传的值是个对象,并且一定要包含 name
,age
字段,类型也加上限定,则是如下写法
List.propTypes = { // 为组件添加校验规则
obj: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
}).isRequired
}
这里 obj
必传,里面的 key 可以不传,若是 key 也想要必传,自行加上 isRequired
还有个默认值,跟 js 语法一致,自行解构出默认值在形参中
对了,方才提到的快捷方式 rfcp
创建组件,这个时候你就理解了
import React from 'react';
import PropTypes from 'prop-types';
const Page = ({page = 10}) => {
return (
<div>
Props的默认值:{page}
</div>
);
};
Page.propTypes = {
};
export default Page;
这样写,props
的默认值就是 10
我们再看下类的写法
import React, { Component } from 'react'
export default class Page extends Component {
static defaultProps = {
page: 10
}
render() {
return (
<div>
Props的默认值:{this.props.page}
</div>
)
}
}
这种写法就没得函数式编程来得舒服,还需要额外定义 static defaultProps
生命周期
所谓生命周期就是代码被读取到的那一刻到页面渲染完成的历程
react 的生命周期相比较 vue 会显得复杂点
先看下常用的几个钩子,记住这张图
整个过程被分成了三部分:挂载时
、更新时
、卸载时
挂载时
挂载时有个 constructor
,这不是类中的构造器吗,然后 render
不是类编程中的函数吗,下面简单写一个类组件
import React, { Component } from 'react'
export default class Life extends Component {
constructor () {
super()
console.log('组件开始加载');
this.name = 'Dolphin'
}
componentDidMount() {
console.log('组件挂载完成');
}
render() {
console.log('组件开始被编译');
return (
<div>
{this.name}
</div>
)
}
}
看下打印顺序
挂载时:先执行 constructor
,此时就是获得数据源,再执行 render
渲染,编译得到虚拟 dom
,中间穿插了个更新 DOM
和 refs
,最后执行 componentDidMount
,这个 did
就是过去时,因此就是 vue 中的 onMounted
,挂载完成。
更新 dom 不是生命周期,只是这个过程在这里发生
render
的目的是生成虚拟 dom,这个时候可以拿到 dom,但是值是空的,下面展示下,先引入 createRef
函数拿到 h4
的 dom,如下
import React, { Component, createRef } from 'react'
export default class Life extends Component {
constructor () {
super()
console.log('组件开始加载', this.ref);
this.name = 'Dolphin'
this.ref = createRef() // 存放dom
}
componentDidMount() {
console.log('组件挂载完成', this.ref);
}
render() {
console.log('组件开始被编译', this.ref);
return (
<div>
<h4 ref={this.ref}>{this.name}</h4>
</div>
)
}
}
运行下看看
这就有点像是 vue 中的 beforeMount
,均拿不到 dom
因此接口请求我们一般写在挂载完成 componentDidMount
中,因为有时候接口请求速度很快,会出现数据拿到了,但是 dom 还没渲染完成,若接口请求还想要操作 dom,就会失败,为了更好的操作 dom,请求我们写在挂载完成后
更新时
更新阶段的 New props
就是拿到父组件传递过来的值,setState
是自身数据源的改变,forceUpdate
也是数据更新的方法,这是强制更新,这三个都会再一次触发 render
,渲染完后又是更新 dom,最后又是更新 componentDidUpdate
。
因此更新阶段的钩子就只有两个,一个 render
,一个 componentDidUpdate
刚刚上面的🌰我改下,添加一个 setState
,就是响应式更改数据源,那就一定会重新触发 render
和 componentDidUpdate
import React, { Component, createRef } from 'react'
export default class Life extends Component {
constructor () {
super()
this.ref = createRef() // 存放dom
this.state = {
count: 1
}
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
componentDidUpdate() {
console.log('组件更新完成');
}
render() {
console.log('组件开始被编译', this.ref);
return (
<div>
<h4 ref={this.ref} onClick={() => this.handleClick()}>{this.state.count}</h4>
</div>
)
}
}
其实 react 中并没有说响应式这个概念,他就是原生 js,
setState
是在数据更新时劫持了这个数据,setState
可以帮我们触发render
,更新视图,若不用setState
,数据是能照常更改,只是视图没有更新罢了
触发 render
操作的还有个 forceUpdate
,这是强制更新,我们可以不用 setState
来改,如下
import React, { Component, createRef } from 'react'
export default class Life extends Component {
constructor () {
super()
this.ref = createRef()
this.count = 1
}
handleClick = () => {
this.count++
this.forceUpdate() // 强制render重新调用
}
componentDidUpdate() {
console.log('组件更新完成');
}
render() {
console.log('组件开始渲染', this.ref);
return (
<div>
<h4 ref={this.ref} onClick={() => this.handleClick()}>{this.count}</h4>
</div>
)
}
}
在某些场景下,我们需要用 forceUpdate
来进行强制更新的
组件更新完成在 react 中只有一个 componentDidUpdate
,而 vue 中就是 beforeUpdate
和 updated
,其实 react 中的 render
就是充当了 beforeUpdate
卸载时
就一个钩子 componentWillUnmount
,组件卸载时执行
componentWillUnmount () {
console.log('组件即将卸载');
}
总结
挂载时
constructor
:类中的构造器充当了生命周期,此时拿到数据源render
:相当于vue中的 beforeMount,还拿不到 domcomponentDidMount
:挂载完成,可以拿到 dom
更新时
render
:父组件传参 New props,修改数据源 setState,强制更新 forceUpdated 都会重新渲染 rendercomponentDidUpdate
:更新完成
卸载时
componentWillUnmount
:组件即将卸载时执行
不常用的生命周期函数
常用的就是上面五个生命周期函数 constructor
、render
、componentDidMount
、componentDidUpdate
和 componentWillUnmount
,不常用的还有,如图,我把不常用的也展开来
static getDerivedStateFromProps()
这是个静态方法,因此这个生命周期函数只能在类组件中使用,这个方法比较冷门,它在render前调用,自行查看罕见用例
shouldComponentUpdate()
这个生命周期函数表示组件该不该更新,若里面返回 false
,就不会给你更新视图,但是数据照常更新
import React, { Component, createRef } from 'react'
export default class Life extends Component {
constructor () {
console.log('组件开始加载');
super()
this.ref = createRef() // 存放dom
this.state = {
count: 1
}
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count);
}
shouldComponentUpdate() {
return false
}
render() {
console.log('组件开始渲染', this.ref);
return (
<div>
<h4 ref={this.ref} onClick={() => this.handleClick()}>{this.state.count}</h4>
</div>
)
}
}
打印下 count
的值,发现确实变化了,但是视图没有更新
getSnapshotBeforeUpdate()
这个函数返回的值会给到 componentDidUpdate
,它在最近一个渲染输出后调用,也就是 render
完之后立马调用
这么看 react 的生命周期函数总共也就是 8 个,和 vue 的生命钩子数量差不多
Demo:todoList
好了,学到这里可以写一个 todolist
了,实现如下效果,可以试着自己实现,后面贴了代码最后比对下
受控组件写法
这里我把 todoList
拆分成两个组件,上面的输入框是一个组件,另一个组件是下面的 ul
列表项,把列表组件导入输入框组件,那就相当于输入框是父组件,列表是子组件
父组件-输入框
import React, { Component } from 'react'
import TodoItem from './TodoItem'
export default class TodoList extends Component {
render() {
return (
<div>
<header>
<input type="text" />
<button>提交</button>
</header>
<section>
<TodoItem />
</section>
</div>
)
}
}
子组件-列表
import React, { Component } from 'react'
export default class TodoItem extends Component {
render() {
return (
<div>
<ul>
<li></li>
</ul>
</div>
)
}
}
父组件中写入 list
数据,把其传给子组件展示,因此涉及一个父子传参,另外我需要拿到 input
框中的 value
值,给到 list
中,这里可以用受控组件来实现
拿到 input
框中的值需要绑定一个点击事件,将 value
值 push
到 list
中,需要重新渲染,有三种方法可以导致重新 render
,一个是父组件的 New props
,这里显然不行,自身就是父组件,一个是 setState
,可以,这需要将 list
赋值给 arr
,然后 arr
进行一个 push
操作,最后将 arr
赋值回 list
,当然也可以用 forceUpdate
进行视图更新
父组件-输入框
import React, { Component } from 'react'
import TodoItem from './TodoItem'
export default class TodoList extends Component {
state = {
list: ['html', 'css', 'js'],
inputVal: ''
}
handleChange = (e) => {
this.setState({
inputVal: e.target.value
})
console.log(this.state.inputVal);
}
handleClick = () => {
}
render() {
return (
<div>
<header>
<input type="text" value={this.state.inputVal} onChange={this.handleChange} />
<button onClick={this.handleClick}>提交</button>
</header>
<section>
<TodoItem list={this.state.list} />
</section>
</div>
)
}
}
子组件-列表
import React, { Component } from 'react'
export default class TodoItem extends Component {
render() {
return (
<div>
<ul>
{
this.props.list.map((item, index) => {
return <li key={index}>{item} <button>x</button></li>
})
}
</ul>
</div>
)
}
}
value
值 push
到 list
中不能直接写 push
, push
的返回值不是数组,而是 push
完数组的长度,下面这种写法是行不通的
this.setState({
list: this.state.list.push(this.state.inputVal)
})
当然我们可以用解构的方式去写,这样更优雅
this.setState({
list: [...this.state.list, this.state.inputVal]
})
这是受控组件写法,下面看下非受控组件的写法
非受控组件写法
非受控写法就是通过引入 createRef
来创建一个容器来拿到 dom
,拿到 input
的 dom
后可以通过 .current.value
拿到 value
值,写法更简洁
import React, { Component, createRef } from 'react'
import TodoItem from './TodoItem'
export default class TodoList extends Component {
inputRef = createRef()
state = {
list: ['html', 'css', 'js'],
inputVal: ''
}
handleClick = () => {
console.log(this.inputRef.current.value);
this.setState({
list: [...this.state.list, this.inputRef.current.value]
})
}
render() {
return (
<div>
<header>
<input type="text" ref={this.inputRef} />
<button onClick={this.handleClick}>提交</button>
</header>
<section>
<TodoItem list={this.state.list} />
</section>
</div>
)
}
}
其实 vue 也可以这样写,人家也是通过 ref
拿到 dom,只不过人家封装了一个 v-model
,完全不需要这样写,很优雅
父组件已经完成了,现在去实现子组件的删除功能
子组件的删除功能
子组件的删除功能,就是删掉父组件的 list
,在react中,父组件的数据源仅仅是可读的
想要实现就是一个子父通信了,需要父组件传递一个函数给子组件,子组件将参数信息 index
给到父组件,父组件这个函数自己来实现删除功能
子组件写删除函数需要进行传参,也就是要写括号,写了括号就一定会自动调用,因此写在 onClick
中的箭头函数体中进行调用,这样才是引用效果
父组件
handleDel = (index) => {
let arr = this.state.list
arr.splice(index, 1)
this.setState({
list: arr
})
}
// ……
<TodoItem list={this.state.list} cb={this.handleDel} />
子组件
onDel = (index) => {
this.props.cb(index)
}
// ……
return <li key={index}>{item} <button onClick={() => this.onDel(index)}>x</button></li>
丢下代码
这里是受控的组件的写法
父组件-输入框
import React, { Component, createRef } from 'react'
import TodoItem from './TodoItem'
export default class TodoList extends Component {
inputRef = createRef()
state = {
list: ['html', 'css', 'js'],
inputVal: ''
}
handleChange = (e) => {
this.setState({
inputVal: e.target.value
})
}
handleClick = () => {
if (this.state.inputVal) {
this.setState({
list: [...this.state.list, this.state.inputVal],
inputVal: ''
})
}
}
handleDel = (index) => {
let arr = this.state.list
arr.splice(index, 1)
this.setState({
list: arr
})
}
render() {
return (
<div>
<header>
<input type="text" value={this.state.inputVal} onChange={this.handleChange} />
<button onClick={this.handleClick}>提交</button>
</header>
<section>
<TodoItem list={this.state.list} cb={this.handleDel} />
</section>
</div>
)
}
}
子组件-列表
import React, { Component } from 'react'
export default class TodoItem extends Component {
onDel = (index) => {
this.props.cb(index)
}
render() {
return (
<div>
<ul>
{
this.props.list.map((item, index) => {
return <li key={index}>{item} <button onClick={() => this.onDel(index)}>x</button></li>
})
}
</ul>
</div>
)
}
}
其实当你可以用一个框架实现一个 todoList
之后,你就可以用这个框架进行项目开发了
最后
react 不同于 vue,没有那么多新的 api,因此上手会比较快。react18 支持我们去写类组件,不过官方还是建议我们去写函数组件,这就像是 vue3 也支持选项式 API 写法,但是官方推荐我们去写组合式 API。接下来的 react 学习就会朝着 hooks
进阶,觉得本期不错,可以关注我,持续带你学习 react
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请”点赞+评论+收藏“一键三连,感谢支持!