原教程视频:ttps://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver
目录
- 一、组件的生命周期
- 1.1、生命周期概念
- 1.2、生命周期流程图(旧)
- 1.3、生命周期流程图(新)
- 1.3.1、getDerivedStateFromProps
- 1.3.2、getSnapshotBeforeUpdata
- 二、React 脚手架
- 2.1、脚手架概念
- 2.2、脚手架文件介绍
- 2.3、编写第一个组件
- 2.4、组件化编码流程
- 三、案例实现
- 3.1、初始化案例
- 3.2、添加todo功能
- 3.3、鼠标悬停效果
- 3.4、删除todo功能
- 3.5、实现底部功能
- 3.6、总结
一、组件的生命周期
生命周期是React中非常重要的一个部分,可以说学了React但不会生命周期 = 白学
1.1、生命周期概念
组件从创建到卸载它会经历一些特定的阶段。React 组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。我们在定义函数时,会在特定的生命周期回调函数中,做特定的工作。
我们通过一个案例来简单描述一下生命周期的运用
需求:定义组件实现以下功能:
- 让指定的文本做显示 / 隐藏的渐变动画
- 从完全可见,到彻底消失,耗时2S
- 点击“不活了”按钮从界面中卸载组件
<script type="text/babel">
class Life extends React.Component{//创建组件
state={opacity:1}
death()=>{
//卸载组件
ReactDOM.unmontComponentAtNode(document.getElementById('test'))
}
//componentDidMount调用的时机:组件挂载完毕
componentDidMount(){//设置一个计时器函数
setInterval(()=>{
let {opactiy}=this.state//获取员状态
opacity-=0.1//减少0.1
if(opacity <= 0) opacity=1//设置新的透明度
this.setState({opacity})
},200)//执行间隔
}
render(){//render调用的时机:初始化渲染、状态更新之后
<div>
<h2 style={{opacity:this.state.opacity}}>React 学不会怎么办?</h2>
<button onClick={this.death}>寄了</button>
</div>
}
}
ReactDOM.render(<Life/>,document.getElementById('test'))//渲染组件
</script>
如此我们完成了上述案例,这里还有个小问题,当我们点击按钮后,会报出异常
意思是不能执行状态更新。其原因是组件已经被卸载,但计时器并未停止计时所导致的。我们需要在组件被卸载的那一刻将计时器清除
代码语言:javascript复制<script type="text/babel">
//创建组件
class Life extends React.Component{
state={opacity:1}
death()=>{
//卸载组件
ReactDOM.unmontComponentAtNode(document.getElementById('test'))
}
//componentDidMount调用的时机:组件挂载完毕
componentDidMount(){//设置一个计时器函数
setInterval(()=>{
//获取员状态
let {opactiy}=this.state
//减少0.1
opacity-=0.1
if(opacity <= 0) opacity=1
//设置新的透明度
this.setState({opacity})
},200)//执行间隔
}
//componentWillUnmount:组件卸载之前
componentWillUnmount()
{
//删除定时器
clearInterval(this.timer)
}
//render调用的时机:初始化渲染、状态更新之后
render(){
<div>
<h2 style={{opacity:this.state.opacity}}>React 学不会怎么办?</h2>
<button onClick={this.death}>寄了</button>
</div>
}
}
//渲染组件
ReactDOM.render(<Life/>,document.getElementById('test'))
</script>
当组件属性需要更新时会调用render()。当组件被挂载到页面时会调用componentDidMount()。当组件被卸载时会调用compentWillUnmount(),就像是人的一生一样
1.2、生命周期流程图(旧)
首先生命周期钩子与顺序无关,当到达了指定的点时React会自己帮我们调用 1.【注意】在调用setState()这个钩子时,它会先去调用shouldComponentUpdata()钩子,这个钩子就会判断一下是否更新组件 2.【注意】当我们没写shouldComponentUpdata()这个钩子时,他的回调一定为true 3. 我们可以调用forceUpdata强制更新组件不需要判断组件是否可以更新 4. 在React中不建议使用组件继承,一般使用组合 5. componentWillReceiveProps这个钩子一般是在第二次render时才调用
总结
- 初始化阶段:由ReactDOM.render()触发——初次渲染 1)constructor() 2)componentWillMount() 3)render() 4)componentDidMount() ====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网路请求、订阅消息
- 更新阶段:由组件内部this.setState()或父组件render触发 1)shouldComponentUpdata() 2)componentWillUpdata() 3)render()====> 必用 4)componentDidUpdata()
- 卸载组件:由ReactDOM.unmountComponentAtNode()触发 1)componentWillUnmount() ====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
1.3、生命周期流程图(新)
改动 所有带Will的钩子都要在前面加上 UNSAFE_,不加会警告,除了componentWillUnmount,为什么要加呢?因为React正在设计一个异步渲染功能,他们总结之前的经验,过时的生命周期往往会带来不安全的编码实践,React官方觉得,这三个钩子在之后的版本潜在的误用问题可能更大
即将废弃三个钩子 ● componentWillMount() ● componentWillReceiveProps() ● componentWillUpdata()
新增了这两个钩子 ● getDerivedStateFromProps() ● getSnapshotBeforeUpdata()
1.3.1、getDerivedStateFromProps
简译:从表单Props中得到一个衍生的状态 这是一个静态的钩子,需要返回状态对象或者null 【注意】返回的状态对象必须与组件状态对的上,并且组件的状态对象对应值也会因此不能更改 【使用场景】当 state 值在任何时候都取决于props时,甚至是更新时也取决于props时使用 派生状态会使代码冗余,并使组件难以维护,所以知道即可
1.3.2、getSnapshotBeforeUpdata
简译:在更新之前获取快照 什么是快照值呢?—— 任何值 【注意】componentDidUpdata(preProps,preState,snapshotValue) 可以传入三个参数,分别是更新前的Props,更新前的State,getSnapshotBeforeUpdata返回的快照值
代码语言:javascript复制class NewList extends React.Component{
render(){
state={newArr:[]}
componentDidMount(){
setInterval(()=>{
//获取原状态
const {newsArr}=this.state
//模拟一条新闻
const news='新闻' (newsArr.length 1)
//更新状态
this.setState({newsArr:[news,...newsArr]})
},1000);
}
getSnapshotBeforeUpdata(){
return this.refs.list.scrollHeight
}
componentDidUpdata(preProps,preState,heigth){
this.refs.list.srollTop = this.refs.list.scrollHeight - heigth
}
return (
<div className="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>})
}
</div>
)
}
ReactDOM.render(<NewList/>,doct)
}
二、React 脚手架
2.1、脚手架概念
xxx脚手架用来帮助程序员快速创建一个基于xxx库的项目模板,其包含了所有需要的配置(语法检查、jsx编译、devServer...)使用脚手架开发的项目能做到模块化、组件化、工程化。下载好了相关依赖,可以直接运行简单效果。
项目整体技术架构为:react webpack es6 eslint。
我们需要会使用npm包管理器 或者 其他包管理器,如yarn。在命令板中
①:输入npm i create-react-app -g
i表示全局。这是将脚手架包安装到全局
②:输入cd 项目文件地址
cd表示改变目录 也可以创建到桌面cd Desktop
③:输入create-react-app react_staging
react_staging为文件名
静止使用中文以及特殊字符
在下载完后往上翻出现Success!就说明代码拉下来了,请确保拉代码时网络环境流畅。
接着在下载好的文件窗口输入cmd
后,在命令窗口输入,npm start
执行后回自动打开浏览器,效果大概是这样。
包管理器比较推荐使用yarn因为是异步下载,比npm快上许多。
2.2、脚手架文件介绍
在vscode中打开项目,通过ctrl ~ 呼出终端窗口,输入npm start 查看当前运行效果
public public ---- 静态资源文件夹 favicon.icon ------ 网站页签图标 index.html -------- 主页面 /只能有一个html页面/ logo192.png ------- logo图 logo512.png ------- logo图 manifest.json ----- 应用加壳的配置文件 robots.txt -------- 爬虫协议文件
src App.css -------- App组件的样式 App.js --------- App组件 App.test.js ---- 用于给App做测试 index.css ------ 样式 index.js ------- 入口文件 logo.svg ------- logo图 reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持) setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)
文件引入简化 可以将各个组件的名称改为index.jsx 这样在引入时,只需要写带有这个组件的文件夹名称即可,因为默认就是index
样式模块化 当组件样式名与出现重复时,后引入的样式就会影响前面的样式,有两种解决方法。 ①:使用less文件的嵌套特性
代码语言:javascript复制.hallo{/*组件名*/
.title{/*结点名*/
background.color:orange;/*属性名*/
}
}
②:React 官方给我们的方法太过繁琐,这里就不演示了
vscode 中react 插件的安装
在vscode插件市场找到上面的插件,它可以让我们通过代码片段快速创建代码模板,比如: 输入rcc 快速创建类式组件需要的代码。 输入rfc 快速创建函数式组件需要的代码。 其他写法可以查阅插件库属性
2.3、编写第一个组件
在public 文件夹/index.html中
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React 脚手架</title>
<link rel="icon" href="%PUBLIC_REL%">
</head>
<body>
<div id="root"></div>
</body>
</html>
在src 文件夹/index.js中
代码语言:javascript复制import React from "react"
//引入ReactDOM
import ReactDOM from "react-dom"
//引入App组件
import App from './App.js'
//渲染APP组件到页面
ReactDOM.render(<App/>,document.getElementById('root'))
在src 文件夹/App.js中
代码语言:javascript复制import React,{Component} from "react";
//创建并暴露 “外壳” 组件App
export default class App extends Component{
render(){
return (
<div>
hallo,react!
</div>
)
}
}
2.4、组件化编码流程
①:拆分组件。当我们拿到一个页面时,需要将它通过功能等特征进行拆分并取好名字,如果命名时感觉无从入手,那多半是拆分的不是很合理
②:实现静态组件,先不考虑交互 ③:实现动态组件,数据类型,数据名称,保存在哪个组件?交互(从绑定事件监听开始)
三、案例实现
案例效果如下
3.1、初始化案例
1):拆分组件
2):写静态组件 相关文件:https://pan.xunlei.com/s/VN44lxOxlIQHx5MEaAwZH1ArA1?path=ary2
将html里的代码cv到App.jsx中,并且以React的编写规则,改一下代码 比如:标签的class得改为className、style中要用双花括号{{}}等等
cv过来时,不用急于拆分,可以把他放App中试一下,确保可以使用后在拆分
然后将样式也cv过来,引入到App.jsx中。 引入时也要规范要求,成型的包(官方已经写好的) > 自己的包(自己写的) > 样式
2.1):拆分布局
Header
代码语言:javascript复制import React, { Component } from 'react'
export default class Header extends Component {
render() {
return (
<div className="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
List
代码语言:javascript复制import React, { Component } from 'react'
import Item from '../Item'
export default class List extends Component {
render() {
return (
<ul className="todo-main">
<Item/>
</ul>
)
}
}
Item
代码语言:javascript复制import React, { Component } from 'react'
export default class Item extends Component {
render() {
return (
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button className="btn btn-danger" style={{display:'none'}}>删除</button>
</li>
)
}
}
Footer
代码语言:javascript复制import React, { Component } from 'react'
export default class Footer extends Component {
render() {
return (
<div className="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button className="btn btn-danger">清除已完成任务</button>
</div>
)
}
}
2.2):拆分样式 Header
代码语言:javascript复制.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
List
代码语言:javascript复制.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
Item
代码语言:javascript复制 li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
Footer
代码语言:javascript复制.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
App
代码语言:javascript复制/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
3):编写动态组件 根据我们现在所学的知识,并不能直接的兄弟组件之间通讯,只能父与子,这样状态等数据只能都交给父组件App来保存了
代码语言:javascript复制 //初始化状态
state={todos:[
{id:'001',name:'吃饭',done:true},
{id:'002',name:'睡觉',done:false},
{id:'003',name:'打代码',done:false},
]}
render() {
const {todos} =this.state
return (
<div className="todo-container">
<div className="todo-wrap">
<Header/>
<List todos={todos}/>/*将状态传到List*/
<Footer/>
</div>
</div>
)
}
App父组件将状态传给子组件List,List又将组件传至更下一级的Item
代码语言:javascript复制import React, { Component } from 'react'
import Item from '../Item'
import './index.css'
import '../Item/index.css'
export default class List extends Component {
render() {
const {todos}=this.props
return (
<ul className="todo-main">
({
todos.map(todo=>{
return <Item key={todo.id} {...todo}/>
})
}
</ul>
)
}
}
我们可以将这个组件需要用到的属性打包成一个对象,通过{...对象名}将整个对象一次性传过去
Item 这边只需要接收展示数据即可
代码语言:javascript复制import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
render() {
const {id,name,done}=this.props
return (
<li>
<label>
<input type="checkbox" defaultChecked={done}/>
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{display:'none'}}>删除</button>
</li>
)
}
}
关于勾选框的defaultChecked和checked属性的区别,defaultChecked表示初始化后续还是可以点;而checked属性就将这个值写死了,需要再写onChange的事件。
3.2、添加todo功能
给Header组件中的input标签添加一个onKeyUp事件 input onKeyUp={this.handleKeyUp}
import React, { Component } from 'react'
import './index.css'
export default class Header extends Component {
handleKeyUp = event=>{
//结构赋值获取keyCode,target
const {keyCode,target}=event
//判断用户按下的按键是否为回车
if(keyCode !== 13)return;
//准备好一个todo对象
const todoObj={id:1,name:target.value,done:false}
this.props.addTodo(todoObj)
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
这里我们希望子组件Header将输入的结果传给父组件,更改父组件的状态,重新渲染List组件
在给Header组件传值时,也可以将函数传过去
代码语言:javascript复制addTodo = todoObj =>{/*addTodo用于添加一个todo,接收的参数是todo对象*/
//获取原todos
const {todos}=this.state
//追加一个todo
const newTodos =[todoObj,...todos]
//更新状态
this.setState({todos:newTodos})
}
render() {
const {todos} =this.state
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo}/>
<List todos={todos}/>
<Footer/>
</div>
</div>
)
在设置组件id属性时需要确保它们之间没有重复
3.3、鼠标悬停效果
值类主要是复习了之前学的事件绑定和编写组件内链样式时的命名规范,item
代码语言:javascript复制import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
/*
初始化状态
mouse:false 鼠标悬停在组件上,默认没有
*/
state ={mouse:false}
//改变mouse属性函数
handleMouse=(flag)=>{
return ()=>{
this.setState({mouse:flag})
}
}
render() {
const {name,done}=this.props
const {mouse}=this.state
return (
/*根据鼠标是否进入状态改变样式 给li标签绑定鼠标移入和移出事件*/
<li style={{backgroundColor:mouse?'#ddd':'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type="checkbox" defaultChecked={done}/>
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{display: mouse?'block':'none'}}>删除</button>
</li>
)
}
}
3.4、删除todo功能
在编写删除todo功能之前,还有个前提条件input的Cheacked值为true。之前我们并未对其进行状态的绑定。首先我们需要明确状态定义在哪操作状态的方法就在那里的概念
这里我们的状态定义在App里面,就从App里面写修改状态的方法
代码语言:javascript复制//updataTodo通知Item组件更新App状态,接受的是item组件的id,以及当前状态
updataTodo = (id,done)=>{
//获取状态中的todos
const {todos} = this.state
//匹配处理对象
const newTodos= todos.map((todoObj)=>{
if(todoObj.id===id) return {...todoObj,done}
else return todoObj
})
this.setState({todos:newTodos})
}
我们定义好方法好直接给List组件传过去,而List本身并不需要使用这个方法,直接传给Item组件,就这样一层层的传下来。Item组件只需要定义一下input标签的onchange事件就行
代码语言:javascript复制//改变checkbox属性函数
handleCheck=(id)=>{
return (event)=>{
this.props.updataTodo(id,event.target.checked)
}
}
接着我们需要对props进行限制,在终端输入npm i prop-types
然后对接收的props进行:类型、必要性的限制
首先引入更改下载好的包import PropsTypes from 'prop-types'
再对props属性进行限制
List
代码语言:javascript复制 static PropsTypes ={
todos:PropsTypes.array.isRequired,
updataTodo:PropsTypes.func.isRequired
}
Header
代码语言:javascript复制static PropsTypes ={
addTodo:PropsTypes.func.isRequired
}
做好这些我们就可以开始编写删除todo功能了,再次复习一下子组件给父组件传值操作,需要在父组件里写好删除函数然后一层一层传下去
先在App.js中写好函数
代码语言:javascript复制//删除指定id的todo对象
deleteTodo=(id)=>{
//获取原来的todos
const {todos}=this.state
//删除指定Id的todo对象
const newTodos = todos.filter((todoObj)=>{
return todoObj.id!==id
})
//更新状态
this.setState({todos:newTodos})
}
传给List -> Item
代码语言:javascript复制//告诉App组件被删除组件的ID
handleDelete=(id)=>{
if(window.confirm('确定要删除嘛?'))
this.props.deleteTodo(id)
}
再绑给onClick事件
3.5、实现底部功能
底部功能分为检测当前todos最大值与状态为完成值,以及清除已完成任务
代码语言:javascript复制export default class Footer extends Component{
render(){
const {todos} = this.props
//已完成的个数
const doneCount = todos.reduce((pre,todo) =>pre (todo.done ? 1 : 0),0)
//总数
const total = todos.length
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.han}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button className="btn btn-danger">清除已完成任务</button>
</div>
}
}
reduce( )方法 可以数组求和、数组去重、二维数组转一维数组、计算数组中每个元素出现的次数
接着我们编写全勾选的功能 App.JSX
代码语言:javascript复制checkAllTodo = () =>{
//获取原来的todos
const {todos} = this.state
//加工数据
const newTodos = todos.map((todoObj)=>{
return {...todoObj,done:true}
})
//更新状态
this.setState({todos:newTodos})
}
render(){
const {todos} = this.state
return (
<div>
<Footer todos={todos} checkAllTodo={this.checkAllTodo}/>
</div>
)
}
Footer.JSX
代码语言:javascript复制//全选Checkbox的回调
handleCheckAll = () =>{
this.props.checkAllTodo()
}
需要注意defaultChecked 与 checked 的区别: defaultChecked 只在刚开始时候调用,后续就改不了了,一般用于初始化设置 checked 必须跟着onChange一起使用
所以这里我们得写checked才能正常使用
代码语言:javascript复制export default class Footer extends Component{
render(){
const {todos} = this.props
//已完成的个数
const doneCount = todos.reduce((pre,todo) =>pre (todo.done ? 1 : 0),0)
//总数
const total = todos.length
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !===0 ? true:false}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button className="btn btn-danger">清除已完成任务</button>
</div>
}
}
checked还需要判断total是否为0,不然全选后删除todo到0个时还会显示全选
接着我们编写清除已完成功能 App.JSX
代码语言:javascript复制//clearAllDone用于清除所有已完成的
clearAllDone = ()=>{
//获取原来的todos
const {todos} = this.state
//过滤数据
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:newTodos})
}
然后将值传给Footer
代码语言:javascript复制<Footer clearAllDone={this.clearAllDone}/>
传了就得用 Footer.JSX
代码语言:javascript复制//清除已完成任务的回调
handleClearAllDone =()=>{
this.props.clearAllDone()
}
render(){
return(
<button onClick={this.handleClearAllDone}>清除已完成任务</button>
)
}
3.6、总结
一、todoList案例相关知识点
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确认将数据放在哪个组件的state中? a. 某个组件使用:放在自身的state中 b. 某些组件使用:放在他们共同的父组件state中(官方称其为:状态提升
- 关于父子之间的通讯: a. 【父组件】给【子组件】传递数据:通过props传递 b. 【子组件】给【父组件】传递数据:通过props传递,要求父组件提前给子组件传递一个函数
- 注意defaultChecked 和 checked 的区别,类式的还有:defaultvalue 和 value
- 状态在哪里,操作状态的方法就在哪里