5-1. 组件拆分
5-2. 静态布局
5-2-1. TodoList组件布局
- 创建对应组件的目录及文件
- 将整体html结构和整体的css,都拷贝到TodoList组件中,完成页面的正常显示
- 修改class ==>
className
5-2-2. 将TodoList组件中的结构和样式拆分到各个组件
5-3. 首屏数据渲染
渲染列表:必须是数组。列表中的数据信息比较多,所以数组的每一项都得是一个对象
根据静态页面效果,分析数据结构如下:
todos = [ { id:1 title:'吃饭' isDone:true }, { id:2, title:'睡觉', isDone:false } ]状态提升
5-4. Footer-总条数-已完成数
let {todos} = this.props
let total = todos.length;
let doneNum = todos.reduce((pre, cur)=>pre + cur.isDone,0)
5-5. 添加代办事项
语言描述要做什么【干什么->效果】:
在头部文本框输入内容,按回车后,可以渲染到列表中
拆分步骤:
- 文本框输入内容:
- 按回车
- 渲染到列表
根据步骤罗列技术点
- 获取文本框输入内容
- 非受控组件 ref:
- e.target:当时间源是文本框的时候,可以有限考虑使用
- 受控组件
- 按回车:
- 绑定键盘事件:
onKeyUp、onKeyDown- 判断按的是不是回车: keycode === 13 或 code == 'Enter'
- 输入的内容渲染到列表:
- 子传父: 数据在哪里,参数数据的方法就要定义在哪里
- 父组件中定义方法
- 方法通过属性传递给子组件
- 子组件调用该方法,传递数据
- 创建一个新的对象 加入到 todos数组中,重置状态即可
-
Header.jsx
- 绑定keyup时间
- 获取文本框输入内容 e.target.value
- 调用父组件方法 传递title
- 清空文本框
import React, { Component } from 'react'
import './index.css'
export default class Header extends Component {
// 通过ref获取
// titleRef = React.createRef()
// 通过受控组件获取
// state = {
// title: ''
// }
// changeHandler(e){
// this.setState({
// title:e.target.value
// })
// }
keyupHandler(e) {
if (e.code === 'Enter') {
// 通过ref获取:
// console.log('ref: ', this.titleRef.current.value.trim())
// 受控组件获取
// console.log('state: ', this.state.title)
// 1. 获取文本框输入的内容
let title = e.target.value.trim()
console.log(title)
// 2. 调用父组件中的 addTodo方法,传递title
this.props.addTodo(title)
// 3. 清空文本框
e.target.value = ''
}
}
render() {
return (
<div className="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} />
{/* <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} ref={this.titleRef} /> */}
{/* <input type="text" placeholder="请输入你的任务名称,按回车键确认"
value={this.state.title}
onKeyUp={this.keyupHandler.bind(this)}
onChange={this.changeHandler.bind(this)}
/> */}
</div>
)
}
}
- TodoList.jsx
- 定义addTodo方法
- 获取header传递过来的title
- 拼接 todo对象
- 生成id : nanoid
- 安装:
npm i nanoid- 导入:
import {nanoid} from 'nanoid'- 生成:
nanoid()- 添加并重置状态todos
- 传递给Header组件
// 添加todo
addTodo(title){
// 1. 获取子组件header中的输入内容title
console.log('TodoList : ',title)
// 2. 拼一个新的todo对象
const todo = {
id:nanoid(),
title,
isDone:false
}
// 3. 加入数组,并执行setState触发更新
this.setState({
todos:[todo, ...this.state.todos]
})
}
5-6. 删除todo
- 描述[干了什么-》效果]:点击删除按钮,删除当前事项
- 步骤:
- 点击按钮
- 删除该条记录
- 技术:
- 点击按钮
- 绑定单击事件: onClick
- 删除该条记录
- 获取当前记录id
- 将id传递到 todos数据所在组件[TodoList组件]
- 在TodoList组件中定义 删除方法,根据id删除
- 重置状态数据,刷新列表
注意:
- 方法定义在TodoList组件中,因为数据在哪儿,操作数据的方法就在哪儿
- 方法要从爷爷组件【TodoList】,传递给孙子组件Item,List做为桥梁
-
TodoList.jsx
定义 deleteById方法
- 接收到item组件传递的 id
- 根据id 过滤todos数据
- 重置状态todos
将
deleteById传递给 List组件
// 根据id删除todo
deleteById(id){
console.log('TodoList: id', id)
// 根据id删除,其实就是保留下,id不相等的记录
let newTodos = this.state.todos.filter(item=>item.id !== id)
// 重置状态数据
this.setState({
todos:newTodos
})
}
- List.jsx
接收TodoList传递的 deleteById 方法,直接传递给Item组件
render() {
let {todos,deleteById} = this.props
return (
<ul className="todo-main">
{todos.map(todo=><Item todo={todo} deleteById={deleteById} key={todo.id}/>)}
</ul>
)
}
- Item.jsx
接收deleteById方法
调用并传递id参数
render() {
let {todo,deleteById} = this.props
return (
<li>
<label>
<input type="checkbox" checked={todo.isDone} onChange={()=>{}}/>
<span className={todo.isDone ? 'done' : ''}>{todo.title}</span>
</label>
<button className="btn btn-danger" onClick={()=>deleteById(todo.id)}>删除</button>
</li>
)
}
5-7. 每条复选框完成已完成切换
- 描述功能:点击复选框,进行完成和未完成切换
- 步骤:
- 点击复选框
- 切换该条记录的完成未完成状态
- 技术点:
- 点击复选框:
- 定义onChange事件
- 切换该条记录的完成未完成状态
- 获取该条记录id
- 将变更切换的方法[checkedOne]定义在 TodoList组件身上
- 将该方法传递给item,执行,并传递id,还要传isDone最新的值
- 在方法 checkedOne 中
- 获取id,获取最新的checked值
- 遍历修改数组中对应id 的 isDone值为checked
- 重置状态todos
- item.jsx
{/*
方式一
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo.id,!todo.isDone)}/>
*/}
// 方式二:
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>
- TodoList.jsx
// checkedOne切换 id条记录的 isDone属性
// 方式一:
// checkedOne(id, isDone){
// console.log('TodoList id: ', id)// 1
// console.log('isDone: ', isDone)// true
// let newTodos = this.state.todos.map(todo=>{
// // 如果是当前的todo,那么修改isDone
// if(todo.id === id){
// todo.isDone = isDone
// }
// return todo
// })
// this.setState({
// todos: newTodos
// })
// }
// 方式二:
checkedOne(todo){
todo.isDone = !todo.isDone
// console.log(this.state.todos)
this.setState({
todos:[...this.state.todos]
})
}
- 方式二原理图
5-8. checkAll
全选反选功能实现
- 获取最新的checked 值
- 将
todos数组中每一个todo的isDone属性修改成 checked
- TodoList.jsx
checkedAll(isDone){
console.log('isDone: ', isDone)
// 使用isDone 去修改todos数组中每一个todo 的isDone属性即可
this.setState({
todos:this.state.todos.map(todo=>{
todo.isDone = isDone
return todo
})
})
// let newTodos = this.state.todos.map(todo=>{
// todo.isDone = isDone
// return todo
// })
// this.setState({
// todos:newTodos
// })
}
- Item.jsx
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>
5-9. 删除已完成
过滤掉
isDone是true的值,保留下isDone是false的数据
- TodoList.jsx
deleteDone(){
this.setState({
todos:this.state.todos.filter(todo=>!todo.isDone)
})
}
- Item.jsx
<button className="btn btn-danger" onClick={deleteDone}>清除已完成任务</button>
5-10. 将todos数据做本地化存储
localStorage
- 存储:
localStorage.setItem(key, value)- 读取:
localStorage.getItem(key)- 移除:
localStorage.removeItem(key)- 清空:
localStorage.clear()需求:
将todos数组存储在 localStorage中:
存储:
JSON.stringify(数组)读取:
JSON.parse(JSON格式字符串)
6. 高阶组件
HOC:H higher O Order C component
高阶函数:函数的参数是函数或者函数的返回值是函数
高阶组件:组件的参数是组件或组件的返回值是组件
高阶组件作用:可以实现类组件代码的复用
6-1. 高阶函数
/**
* 函数的参数是函数,或函数的返回值是函数,就是高阶函数
*
* const app = express()
*
* // 中间件
* app.use((req,res,next)=>{
*
* })
*
*/
function sum(a,b,c){
return a + b + c;
}
console.log(sum(1,2,3))
// 高阶函数sum
// 柯里化函数:参数分步骤传递
// 为什么分开传?
//
function sum2(a){
return function (b){
return function (c){
return a + b + c;
}
}
}
let res = sum2(1)(2)(3)
console.log('res: ', res)
/** 1000 5000 3000 50个亿 3000万
* 完事具备:资金 、 场地、 人员、 关系、 资源 、 粉丝、
* 啥也没有:1000、 5000 3000
*
*/
6-2. 高阶组件
// 1.0
// function WithApp(Com){
// return Com
// }
import {Component} from 'react'
// 2.0
// function WithApp(Com){
// return class extends Component{
// render(){
// return <Com/>
// }
// }
// }
// 3.0 属性代理
// function WithApp(Com){
// return class extends Component{
// render(){
// return <Com username="atguigu" age="10"/>
// }
// }
// }
// 4.0 属性代理
// function WithApp(Com,props){
// return class extends Component{
// render(){
// return <Com {...props}/>
// }
// }
// }
// 5.0 参数分阶段传递
// function WithApp(props){
// return function (Com){
// return class extends Component{
// render(){
// return <Com {...props}/>
// }
// }
// }
// }
// 6. 0 状态数据和props一起传递给子组件Com
// function WithApp(props){
// return function (Com){
// return class extends Component{
// state = {
// ...props,
// money:1000
// }
// render(){
// return <Com {...this.state}/>
// }
// }
// }
// }
// 7.0
function WithApp(mapStateToProps){
return function (Com){
return class extends Component{
state = {
...mapStateToProps(),
money:1000
}
render(){
return <Com {...this.state}/>
}
}
}
}
export default WithApp
6-3. 属性代理实现代码复用
高阶组件的作用:就是为了实现类组件的逻辑的复用
- hoc/WithMove.jsx
import { Component } from "react";
/**
*
* @param {*} WC W Wrapper C component
*/
export default function WithMove(WC) {
return class extends Component {
state = {
x: 120,
y: 60
}
moveHandler(e) {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.moveHandler.bind(this))
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.moveHandler.bind(this))
}
render(){
return <WC {...this.state}/>
}
}
}
- components/TianE.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'
class TianE extends Component {
render() {
let {x,y} = this.props
return (
<div style={{width:100,height:100,border:'1px solid red',position:'absolute',left:x,top:y}}>尚硅谷的女老师们!!!</div>
)
}
}
export default WithMove(TianE)
- components/HaMa.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'
class HaMa extends Component {
render() {
let {x,y} = this.props
x += 110
y += 110
return (
<div style={{width:100,height:100,border:'1px solid green',position:'absolute',left:x,top:y}}>
晶哥!!!
</div>
)
}
}
export default WithMove(HaMa)
- App.jsx
import React, { Component } from 'react'
import HaMa from './components/HaMa'
import TianE from './components/TianE'
export default class App extends Component {
render() {
return (
<div>
<TianE/>
<HaMa></HaMa>
</div>
)
}
}
7. Hook
7-1. useState
给函数组件创建状态数据的
语法:
let [状态, 设置状态函数] = useState(初始值)
7-1-1. setXxx 函数的两种用法
-
setXxx(要赋的值): -
setXxx(回调函数):- 回调函数的参数: 最新的变化后的状态数据
- 回调函数的返回值:要设置的最新的值
应用:当使用方式一设置值不正确,发现无法获取到最新的数据时,可能是由于闭包导致的。所以,要考虑使用方式二
import React, {useEffect, useState} from 'react'
export default function App() {
let [msg, setMsg] = useState('atguigu')
useEffect(()=>{
// componentDidMount
setTimeout(()=>{
// 方式一:
// msg 只能取到初始值 atguigu, 并不能取到最新变化后的值
// setMsg(msg + '_')
// 方式二:
// 参数是一个回调函数,回调函数的参数,是最新的变化后的状态数据
// 回调函数的返回值,是设置的最新的状态数据的值
setMsg((msg)=>{
return msg + '_'
})
},2000)
},[])
return (
<div>
<h3>msg: {msg}</h3>
<p><button onClick={()=>{
setMsg(msg + '+')
}}>msg + </button></p>
</div>
)
}
7-2. useEffect
作用:模拟生命周期函数
componentDidMountcomponentDidUpdatecomponentWillUnmount注意:
useEffect可以调用多次// 可以同时调用多次 useEffect(()=>{ console.log('didMount2') },[]) // 可以同时调用多次 useEffect(()=>{ console.log('didMount3') },[money])
用法一:只有第一个参数回调函数,没有第二个参数
useEffect(()=>{ // componentDidMount + componentDidUpdate[所有state的变化都触发 + 所有的props变化也触发]
console.log('用法一:run')
})
// 用法二: 第二个参数是一个空数组,单独模拟componentDidMount
// useEffect(()=>{ // componentDidMount
// console.log('用法二:')
// },[])
// 用法三: 第二个参数是数组,数组中指定监听的元素
// useEffect(()=>{ // componentDidMount + componentDidUpdate[监听谁 谁变化]
// console.log('用法三:')
// },[money,count])
// 用法四:同时模拟 componentDidMount- 挂载 和 componentWillUnmount-卸载
useEffect(()=>{
// componentDidMount()
console.log('挂载后触发')
return ()=>{
// 这里模拟的是 componentWillUnmount
console.log('componentWillUnmount - 模拟组件销毁是触发')
}
},[])
7-3. useRef
作用:创建ref对象,在函数组件中绑定获取真实
dom元素用法:
引入:
import {useRef} from 'react'创建:
const inputRef = useRef()绑定:
<input type='text' ref={inputRef} />获取:
inputRef.current
7-3-1. 受控组件
import React, { useRef, useState } from 'react'
export default function App() {
// 定义状态数据
let [username, setUsername] = useState('atguigu')
let [pwd, setPwd] = useState('123')
let [sex, setSex] = useState("1")
let [city, setCity] = useState('1')
let [hobby, setHobby] = useState(["1","3"])
const submitHanlder = (e)=>{
e.preventDefault()
}
// function usernameChange(e){
// console.log(e.target.value)
// setUsername(e.target.value)
// }
// function pwdChange(e){
// setPwd(e.target.value)
// }
function changeHandler(fn){
return (e)=>{
let value = e.target.value
if(e.target.type === 'checkbox'){
let index = hobby.findIndex(item=>item === e.target.value)
value = [...hobby]
if(index === -1)value.push(e.target.value)
else value.splice(index, 1)
}
fn(value)
}
}
return (
<div>
<div>
<form action="" onSubmit={submitHanlder}>
<p>username: <input type="text" name="" value={username} onChange={changeHandler(setUsername)}/></p>
<p>pwd: <input type="text" name="" value={pwd} onChange={changeHandler(setPwd)}/></p>
<p>性别:
<input type="radio" name="sex" value="1" checked={sex==="1"} onChange={changeHandler(setSex)}/> 男
<input type="radio" name="sex" value="0" checked={sex==="0"} onChange={changeHandler(setSex)}/> 女
</p>
<p>city:
<select name="" value={city} onChange={changeHandler(setCity)}>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">深圳</option>
</select>
</p>
<p>爱好:
<input type="checkbox" name="" value="1" checked={hobby.includes("1")} onChange={changeHandler(setHobby)}/> 台球
<input type="checkbox" name="" value="2" checked={hobby.includes("2")} onChange={changeHandler(setHobby)}/> 滑冰
<input type="checkbox" name="" value="3" checked={hobby.includes("3")} onChange={changeHandler(setHobby)}/> 潜水
<input type="checkbox" name="" value="4" checked={hobby.includes("4")} onChange={changeHandler(setHobby)}/> 撑杆跳
</p>
<p><button>提交</button></p>
</form>
</div>
</div>
)
}
7-3-2. 非受控组件
import React, { useRef, useState } from 'react'
export default function App() {
// 定义状态数据
let [username, setUsername] = useState('atguigu')
let [pwd, setPwd] = useState('123')
let [sex, setSex] = useState("1")
let [city, setCity] = useState('1')
let [hobby, setHobby] = useState(["1","3"])
// 定义ref
const divRef = useRef()
const usernameRef = useRef()
const pwdRef = useRef()
const sexRef = [useRef(), useRef()]
const cityRef = useRef()
const hobbyRef = [useRef(), useRef(), useRef(), useRef()]
const submitHanlder = (e)=>{
e.preventDefault()
console.log('非受控获取提交数据')
console.log('username: ', usernameRef.current.value.trim())
console.log('pwd: ', pwdRef.current.value.trim())
console.log('sex: ', sexRef.find(item=>item.current.checked).current.value)
console.log('city: ', cityRef.current.value)
console.log('hobby: ', hobbyRef.filter(item=>item.current.checked).map(item=>item.current.value))
}
return (
<div>
<h3>App</h3>
<div ref={divRef}>div</div>
<p><button onClick={()=>{
console.log('div dom: ', divRef.current)
}}>获取真实Dom</button></p>
<div>
<form action="" onSubmit={submitHanlder}>
<p>username: <input type="text" name="" defaultValue={username} ref={usernameRef}/></p>
<p>pwd: <input type="text" name="" defaultValue={pwd} ref={pwdRef}/></p>
<p>性别:
<input type="radio" name="sex" ref={sexRef[0]} value="1" defaultChecked={sex==="1"}/> 男
<input type="radio" name="sex" ref={sexRef[1]} value="0" defaultChecked={sex==="0"}/> 女
</p>
<p>city:
<select name="" defaultValue={city} ref={cityRef}>
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">深圳</option>
</select>
</p>
<p>爱好:
<input type="checkbox" name="" defaultChecked={hobby.includes("1")} value="1" ref={hobbyRef[0]}/> 台球
<input type="checkbox" name="" defaultChecked={hobby.includes("2")} value="2" ref={hobbyRef[1]}/> 滑冰
<input type="checkbox" name="" defaultChecked={hobby.includes("3")} value="3" ref={hobbyRef[2]}/> 潜水
<input type="checkbox" name="" defaultChecked={hobby.includes("4")} value="4" ref={hobbyRef[3]}/> 撑杆跳
</p>
<p><button>提交</button></p>
</form>
</div>
</div>
)
}
7-4. Hook使用规则
- React钩子函数,不能在类组件中调用
- React Hooks必须在React函数组件或自定义React Hook函数中调用
- hook函数使用时数量必须是确定的【要在顶级作用域中使用】
import React, { useState,Component } from 'react'
function App() {
let [count,setCount] = useState(10)
// 报错:hook函数不能在普通函数中使用
// function addTodo(){
// let [msg, setMsg] = useState('atguigu')
// }
// 不能在循环、判断中使用,必须是确定数量的【完全相同的顺序】
// if(true){
// let [msg, setMsg] = useState('atguigu')
// }
// for(let i=0;i<100;i++){
// let [msg, setMsg] = useState('atguigu')
// }
// if(count == 1){
// return
// }
// let [msg, setMsg] = useState('atguigu')
return (
<div>App</div>
)
}
// 报错: React Hook "useState" cannot be called in a class component. React钩子“useState”不能在类组件中调用
// React Hooks must be called in a React function component or a custom React Hook function React Hooks必须在React函数组件或自定义React Hook函数中调用
//
// class App extends Component{
// render(){
// let [msg, setMsg] = useState('atguigu')
// return (
// <div>App</div>
// )
// }
// }
export default App
7-5. 自定义hook函数
hook 本质就是一个函数,hook函数和普通函数有什么区别呢?
区别在函数名
hook函数:函数名必须是以
useXxx开头的。 例如:usePostion、useMyHook
import { useEffect, useState } from "react";
/**
* 遵循 useXxx命名法,react就认为是hook函数。
* 在自定义hook中使用 其他hook,不报错
*/
export default function useMyHook(){
let [x, setX] = useState(0)
useEffect(()=>{
console.log('sjdfjlkas')
})
}
/**
* 既不是函数组件【首字母没大写】、也不是其他hook【没有use开头】
* 所以,内部不能使用 其他hook
*/
// export default function myHook(){
// let [x, setX] = useState(0)
// useEffect(()=>{
// console.log('sjdfjlkas')
// })
// }
7-6. context
祖先组件向后代组件传参
使用步骤:
创建并导出 context:
export default React.createContext()使用
context.Provider组件包裹后代组件,并传递数据<context.Provider value={{数据}} > 后代组件 </context.Provider>在后代组件中获取数据
引入context:
import context from './context.js'使用useContext获取值
let {数据} = useContext(context)
- context.js
import React from 'react'
// 创建一个context对象并导出
export default React.createContext()
- App.jsx
import React from 'react'
import Father from './components/Father'
import context from './context'
export default function App() {
return (
<div style={{ width: 500, height: 500, border: '1px solid red' }}>
{/* 使用context中的Provider组件包裹,后代组件,并传递数据value */}
<context.Provider value={{username:'atguigu'}}>
<h3>App</h3>
<Father></Father>
</context.Provider>
</div>
)
}
- 后代组件中
import React, { useContext } from 'react'
import Son from './Son'
// 导入context对象
import context from '../context'
export default function Father() {
let { username } = useContext(context)
return (
<div style={{ width: 300, height: 300, border: '1px solid blue' }}>
<h4>Father</h4>
<p>username: {username}</p>
<hr />
<Son />
</div>
)
}
7-7. pubsub
作用:实现任意组件间的通信
原理:利用发布订阅原理
使用:
安装:
npm i pubsub-js导入:
import PubSub from 'pubsub-js'Api
- 订阅消息:
let 消息id = PubSub.subscribe(消息名, (消息名, 数据)=>{})- 发布消息:
PubSub.publish(消息名, 数据)- 取消订阅:
- 取消自己的订阅:
PubSub.unsubscribe(消息id)- 取消该消息名的所有消息:
PubSub.unsubscript(消息名)- 取消所有消息:
clearAllSubscriptions
8. ajax
- 安装axios
- 配置:
- 使用:
src/request/index.jsx 对axios进行全局配置
// 2. 导入axios
import axios from 'axios'
// 3. 配置axios
const request = axios.create({
baseURL: 'https://api.github.com/',
timeout: 20000
})
// 4. 配置响应拦截器-简化响应的数据
request.interceptors.response.use(response=>response.data)
export default request
- 在组件中使用 App.jsx
import React, { useEffect } from 'react'
import request from './request'
export default function App() {
// 一般首屏数据渲染,会在componentDidMount生命周期中发送ajax请求
useEffect(() => {
// useEffect的回调不能用async修饰,需要单独定义一个async函数并调用
async function getRepo() {
let res = await request.get('/search/repositories', {
params: {
q: 'vue',
sort: 'stars'
}
})
console.log('res: ', res)
}
getRepo()
}, [])
return (
<div>App</div>
)
}
9. 路由
10. redux
大型项目,组件间进行数据通信,数据的管理和维护比较麻烦。我们希望对数据进行统一的管理。使用redux
redux: 统一进行数据状态管理的库。
10-1. 三个核心概念:
10-1-1. store: 数据仓库
10-1-2. reducer[执行者]:专门操作仓库数据的函数
10-1-3. action: 提出修改数据的需求
10-2. redux基本使用
-
安装:
npm install @reduxjs/toolkit -
导入:
import {createSlice} from '@reduxjs/toolkit' -
创建 切片
// 1. 安装 :
// 2. 导入 createSlice 模块
// create 创建
// Slice 切片【模块】
import {createSlice} from '@reduxjs/toolkit'
// 3. 创建切片
const countSlice = createSlice({
// 切片名
name:'count',
// initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
initialState:{
num:1000,
msg:'atguigu'
},
// 干活的程序员[ 函数 ]
// 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
// 该方法的身份是actionCreator,作用是用来创建action对象的
reducers:{
/**
*
* @param {*} state :状态数据
* @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据}
* type:属性给程序看的,不是给程序员使用的
*/
addNum: (state, action)=>{
state.num += action.payload
},
decNum:(state,action)=>{
state.num -= action.payload
}
}
})
console.log('countSlice: ',countSlice)
// 解构出 actionCreator : addNum
const {addNum} = countSlice.actions
let res = addNum(1000) // 使用addNum actionCreator 创建一个action
console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
- 创建仓库
// 1. 安装 :
// 2. 导入 createSlice 模块
// create 创建
// Slice 切片【模块】
// : 创建数据仓库的模块
import {createSlice, configureStore} from '@reduxjs/toolkit'
// 3. 创建切片
const countSlice = createSlice({
// 切片名
name:'count',
// initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
initialState:{
num:1000,
msg:'atguigu'
},
// reducers中定义:干活的程序员[ 函数 ]
// 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
// 该方法的身份是actionCreator,作用是用来创建action对象的
reducers:{
/**
*
* @param {*} state :状态数据
* @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据}
* type:属性给程序看的,不是给程序员使用的
*/
addNum: (state, {payload})=>{
state.num += payload
},
decNum:(state,action)=>{
state.num -= action.payload
}
}
})
// 以下是打印测试切片的代码
// console.log('countSlice: ',countSlice)
// // 解构出 actionCreator : addNum
const {addNum} = countSlice.actions
// let res = addNum(1000) // 使用addNum actionCreator 创建一个action
// console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
// 创建仓库 store
const store = configureStore({
reducer:{
count:countSlice.reducer
}
})
// 可以对状态数据进行监听,如果有变化,就触发回调函数的执行
store.subscribe(()=>{
console.log('订阅数据变了')
console.log(store.getState())
})
// 查看状态数据
// console.log(store.getState())
// 修改数据
// dispatch: 分发,参数是一个action对象,根据action对象中的 type,来找到对应切片中的对象应发完成任务
store.dispatch(addNum(30))
store.dispatch(addNum(50))
store.dispatch(addNum(100))
10-3. 添加商品
/**
* 使用redux 管理商品数据
* 1. 可以添加商品数据
*
*/
import {createSlice, configureStore} from '@reduxjs/toolkit'
// 1. 创建切片
const goodsSlice = createSlice({
name:'goods',
initialState:{
goodsList:[]
},
reducers:{
addGoods(state, {payload}){
// 实现向goodsList数组中添加数据
// console.log('payload', payload)
let goods = {
...payload,
// 0-9 a-z = 36
// 0.1231532234547643
id: Math.random().toString(36).slice(2)
}
// console.log('goods', goods)
state.goodsList.push(goods)
}
}
})
// 2. 创建仓库
const store = configureStore({
reducer:{
goods: goodsSlice.reducer
}
})
// 3. 监听仓库
store.subscribe(()=>{
console.log('数据: ', store.getState())
})
// 4. 添加商品
// 获取actionCreator
const {addGoods} = goodsSlice.actions
// {
// gname:'商品名',
// price:1999
// }
store.dispatch(addGoods({gname:'小米手机', price:1999}))
store.dispatch(addGoods({gname:'华为笔记本', price:7000}))
10-4. store模块化
src
|- store 整个redux数据目录
| |- slice 所有切片目录
| |- index.js 创建的store仓库入口文件
- store/slice/goodsSlice
import { createSlice } from '@reduxjs/toolkit'
// 1. 创建切片
const goodsSlice = createSlice({
name: 'goods',
initialState: {
goodsList: []
},
reducers: {
addGoods(state, { payload }) {
// 实现向goodsList数组中添加数据
// console.log('payload', payload)
let goods = {
...payload,
// 0-9 a-z = 36
// 0.1231532234547643
id: Math.random().toString(36).slice(2)
}
// console.log('goods', goods)
state.goodsList.push(goods)
}
}
})
/**
* 一个切片,大体暴露以下几类东西
* 1. 默认暴露: reducer
* 2. 分别暴露:actionCreator:
* 3. 分别暴露:异步操作的方法
*/
export default goodsSlice.reducer
export const {addGoods} = goodsSlice.actions
- store/index.js
import {configureStore} from '@reduxjs/toolkit'
import goods from './slice/goodsSlice'
// 2. 创建仓库
const store = configureStore({
reducer: {
goods
}
})
export default store
- 调用使用: src/index.js
// 1. 导入仓库
import store from "./store";
// 2. 导入actionCreator
import {addGoods} from './store/slice/goodsSlice'
store.subscribe(()=>{
console.log('数据: ', store.getState())
})
// 2. 添加商品
store.dispatch(addGoods({
gname:'小米手机',
price:1999
}))
store.dispatch(addGoods({
gname:'华为手机',
price:5999
}))
11. redux小练习
需求:
点击按钮发送ajax请求,获取一句话,将一句话的长度累计到 redux状态数据
文件介绍:
- request/index.js: axios 统一配置文件【基础路径、响应拦截器简化返回数据】
- api/onSentence.js: 所有跟一句话相关的请求接口方法
- store: 数据仓库
- store/slice/countSlice:
- 定义状态数据num
- 使用createAsyncThunk 创建了异步的actionCreate
- 调用了api接口
- extraReducer中,添加了case,处理请求后的数据
- App.jsx
- 展示状态数据
- 点击按钮,触发异步的actionCreator方法
- src/index.js
- 导入Provider
- 包裹根组件,传递store数据
12-redux-todoslist-综合练习
todos数据来源于数据库,在前端使用redux进行管理。因此,需要保证远端服务器数据和redux中的数据是同步的。因此,在进行用户交互操作的时候, 如果改动了数据,需要修改远端数据库的数据,也要修改redux中管理的数据。
- 修改远端服务器数据:在异步的actionCreator中进行
- 修改redux中的数据:在addCase中进行
12-1. request和api的封装
- request/index.js
import axios from 'axios'
// 配置baseURL和超时时间
const request = axios.create({
baseURL:'http://localhost:7000',
timeout:20000
})
// 响应拦截器,简化服务器端返回数据
request.interceptors.response.use(response=>response.data)
export default request
-
src/api/todos.js
根据后端接口文档【或源码】,封装前端发送ajax的请求函数
import request from '../request'
/**
* 获取素有的todolist数据
* @returns
*/
export const getTodos = ()=>{
return request.get('/todos')
}
/**
* 添加todo
* @param {*} todo {title:xxx,isDone:false}
* @returns
*/
export const addTodo = (todo)=>{
return request.post('/todos', todo)
}
/**
* 根据id删除todo
* @param {*} _id
* @returns
*/
export const deleteById = (_id)=>{
return request.delete('/todos/' + _id)
}
/**
* 根据id 修改isDone的值
* @param {*} todo {_id:xxx, title:xxx, isDone:false}
* @returns
*/
export const updateIsDone = (todo)=>{
return request.patch('/todos/'+ todo._id, todo)
}
/**
* checkAll ,全选反选
* @param {*} data {isDone: false/true}
* @returns
*/
export const checkAll = (data)=>{
return request.patch('/checkAll', data)
}
/**
* 清除已完成
* @returns
*/
export const deleteChecked = ()=>{
return request.get('/deleteChecked')
}
12-2. 首屏数据渲染
获取服务器端 todoList数据,初始化redux中的状态数据。在组件中读取 redux中的todos数据,进行渲染
12-2-1. 创建store
- store/index.js
import { configureStore } from "@reduxjs/toolkit";
import todoList from './slice/todoSlice'
const store = configureStore({
reducer:{
todoList
}
})
export default store
-
store/slice/todoSlice.js
- 定义异步的actionCreator: 发送ajax请求,获取所有todos数据
- addCase: 将请求回来的todos数据,初始化redux中的 状态数据 todos
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit'
// 导入api方法
import {getTodos} from '../../api/todos'
const todoSlice = createSlice({
name:'todoList',
initialState:{
todos:[]
},
extraReducers:builder=>{
// 初始化todos
builder.addCase(asyncGetTodos.fulfilled, (state, {payload})=>{
state.todos = payload
})
}
})
/**
* 发送ajax请求 初始化 todos
*/
export const asyncGetTodos = createAsyncThunk('todoList/getTodo', async (payload)=>{
let {todos} = await getTodos()
return todos
})
export default todoSlice.reducer
- src/index.js
导入Provider,包裹App根组件, 传递store
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
12-2-2. 组件加载完之后,compomentDidMount中,发送ajax请求
- App.jsx 中
export default function App() {
const dispatch = useDispatch()
useEffect(()=>{
// 初始化todos
dispatch(asyncGetTodos())
},[])
return (
<>
<TodoList />
</>
)
}
- List组件中,获取redux中的todos,并进行遍历渲染
import React from 'react'
import { useSelector } from 'react-redux'
import Item from '../Item/Item'
export default function List() {
let {todos} = useSelector(state=>state.todoList)
return (
<ul className="todo-main">
{todos.map(todo=><Item key={todo._id} todo={todo}/>)}
</ul>
)
}
13. 其他hook
13-1. useRef-React.forwardRef-useImpretiveHandle
useRef:
绑定dom元素,获取真实dom
可以单独模拟componentDidUpdate
给以给类组件绑定,获取类组件的实例对象
给函数组件绑定:
4-1. 本身不支持
4-2. 函数组件使用 React.forwardRef 包裹再导出
4-2-1. 函数组件的第二个参数,可以收到父组件的ref对象
4-2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法
- App.jsx
import React, { useRef } from 'react'
import { useEffect } from 'react'
import ClassTest from './components/ClassTest'
import FunTest from './components/FunTest'
/**
* 1. 绑定dom元素,获取真实dom
* 2. 可以单独模拟componentDidUpdate
* 3. 给以给类组件绑定,获取类组件的实例对象
* 4. 给函数组件绑定:
* 1. 本身不支持
* 2. 函数组件使用 React.forwardRef 包裹再导出
* 2-1. 函数组件的第二个参数,可以收到父组件的ref对象
* 2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法
*
*/
export default function App() {
let flagRef = useRef(true)
console.log(flagRef)
useEffect(() => { // componentDidMount + componentDidUpdate
if (flagRef.current) {
// didMount
flagRef.current = false
return;
}
// didUpdate
})
// 类组件ref
let classRef = useRef()
// 函数组件ref
let funRef = useRef('fun')
return (
<div>
<h3>类组件</h3>
<ClassTest ref={classRef} />
<hr />
<h3>函数组件</h3>
{/* 函数组件本身不能绑定ref */}
{/*
可以使用React.forwordRef处理函数组件后,进行ref的绑定
可以在父组件中操作子组件的真实dom元素
*/}
<FunTest ref={funRef} />
<button onClick={() => {
console.log(classRef.current) // ref可以绑定给类组件获取类组件的实例对象
//
console.log(funRef.current)
funRef.current.changeColor()
funRef.current.changeSize()
}}>获取ref</button>
</div>
)
}
- ClassTest.jsx
import React, { Component } from 'react'
export default class ClassTest extends Component {
state = {
msg:'类组件'
}
render() {
return (
<div>ClassTest</div>
)
}
}
- FunTest.jsx
import React,{useImperativeHandle, useRef} from 'react'
function FunTest(props,ref) {
console.log('ref: ', ref)
// 子组件自己的ref
let selfRef = useRef()
useImperativeHandle(ref, ()=>({
changeColor:()=>{
selfRef.current.style.color = 'blue'
},
changeSize:()=>{
selfRef.current.style.fontSize = '100px'
}
}))
return (
<div ref={selfRef}>FunTest</div>
)
}
export default React.forwardRef(FunTest)
13-2. useReducer[了解]
模拟一个小型的 redux,做简易版的集中状态管理
- App.jsx
import React from 'react'
import Test1 from './components/Test1'
export default function App() {
return (
<div>
<Test1/>
</div>
)
}
- Test1.jsx
import React, { useReducer } from 'react'
const initialState = { count: 0, msg: '哈哈' }
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + 1,
}
case 'decrement':
return {
...state,
count: state.count - 1,
}
default:
throw new Error()
}
}
export default function Test1() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: {state.count} <br />
msg: {state.msg} <br />
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}
13-3. useCallback 【掌握】
避免组件更新,函数重新创建。
setXxx 时,要使用 回到函数,获取到最新的状态数据
import React, { useCallback, useState } from 'react'
/**
* useCallBack 可以缓存函数,避免组件更新,函数重新创建
*
*/
export default function App() {
let [count, setCount] = useState(100)
function addCount(){
setCount(count=>{
return count + 1
})
}
const handle = useCallback(addCount, [])
return (
<div>
<p>count: {count}</p>
<p><button onClick={handle}>count + </button></p>
</div>
)
}
13-4. useMemo【掌握】
缓存一个函数运行的结果
import { useMemo, useState } from "react";
export default function After() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return <div>
<h4>{count}-{expensive}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
13-5. React.memo【掌握】
类似类组件的纯组件功能,当自身状态数据state和外部数据props没有变化的时候,不重新渲染
- App.jsx
import React, { useState,Component } from 'react'
import Son from './components/Son'
/**
*
* 函数组件自身状态数据没有变化,已经做过优化,只多渲染一次
*/
export default class App extends Component {
state = {
count:100
}
render(){
let {count} = this.state
console.log('app render')
return (
<div>
<p>count:{count}</p>
<Son count={count}/>
<p><button onClick={()=>{
this.setState({
count:111
})
}}>count+</button></p>
</div>
)
}
}
- Son.jsx
import React from 'react'
import { useState } from 'react'
/**
* 函数组件对state,自身状态数据,不变时,只多渲染一次
* 函数组件没有对props数据不变的情况做优化
*
*/
function Son({count}) {
let [msg, setMsg] = useState('atguigu')
console.log('son render')
return (
<div>
<p>props-count: {count}</p>
<p>state-msg: {msg}</p>
<p><button onClick={()=>{
setMsg('atguigu123123')
}}>msg- change</button></p>
</div>
)
}
export default React.memo(Son)
13-6. useLayoutEffect[了解]
优化页面呈现,避免出现位置移动的闪屏
- Animate.jsx
import React, { useEffect, useLayoutEffect, useRef } from 'react'
import TweenMax from 'gsap' // npm i gsap@3.7.0
import './index.css'
const Animate = () => {
const REl = useRef(null)
useLayoutEffect(() => {
/*下面这段代码的意思是当组件加载完成后,在0秒的时间内,将方块的横坐标位置移到600px的位置*/
TweenMax.to(REl.current, 0, { x: 600 })
}, [])
return (
<div className="animate">
<div ref={REl} className="square">
square
</div>
</div>
)
}
export default Animate
- index.css
.square{
width:100px;
height:100px;
border:2px solid red;
}
附录
1. react代码片段
{
"react模板":{
"prefix": "!react",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<title>Title</title>",
"\t<script src=\"./lib/react.development.js\"></script>",
"\t<script src=\"./lib/react-dom.development.js\"></script>",
"\t<script src=\"./lib/babel.min.js\"></script>",
"</head>",
"<body>",
"\t<div id=\"root\"></div>",
"</body>",
"<script type=\"text/babel\">",
"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
"\troot.render((",
"\t\t<div></div>",
"\t))",
"</script>",
"</html>"
],
"description": "快速构建react模板页页面"
}
}
1-3. 注意事项
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<div class="box"></div>
</body>
<script>
{
// 1. 可以在同一个页面有多个容器
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('root容器')
// const box = ReactDOM.createRoot(document.querySelector('.box'))
// box.render('box 容器')
}
{
// 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('首次渲染')
// root.render('二次渲染')
}
{
// 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root1.render('root 一')
// root2.render('root 二')
}
{
// 4. 不能将 html body 指定为 react容器
// html
// const root = ReactDOM.createRoot(document.documentElement);
// root.render('html')
//
// const root = ReactDOM.createRoot(document.body);
// root.render('body')
}
{
// 5. 支持链式调用
ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
}
</script>
</html>
1-3. 虚拟DOM和真实DOM
<script>
/**
* react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
* 1. 页面是如何渲染的?
* 地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
* 1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
* 1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
* 1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
*
* 2. 什么是重排和重绘?
* css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
*
* 3. 传统DOM操作的缺陷!
* 频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
* this.style.height = 100px;
* this.style.left = '200px'
*
* 4. React、vue 性能更好,为什么?
* React 和 vue引入了虚拟dom 优化性能
*
* 5. 虚拟DOM是什么?
* 1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
* 2. 虚拟DOM 通过 render 可以转化为真实DOM
* 3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
* 4. 虚拟dom 在react中,我们又称之为时 react元素
* 5. 虚拟dom的创建有两种方法
* 1. 通过 React.createElement() 创建
* 2. 通过 jsx 创建
*
*/
</script>
1-4. call apply bind区别
call apply 立即执行
bind:返回一个新函数,不执行
let obj = {name:'atguigu'}
function getSonData(a){
console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])
// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()
1-5. 快速创建 类组件和函数组件的快捷键
rfc: react function component
rcc: react class component
1-6. class-普通方法-箭头函数方法
普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。
直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系
class Person{
constructor(name,age){
this.name = name;
this.age = age;
// this.eat = ()=>{
// console.log(this)
// console.log(this.name + '想吃饭了')
// }
// this.state = {
// count:1
// }
}
// say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
say(){
console.log('say this: ', this)
console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
}
// 通过=号赋值,定义箭头函数
// eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
eat = ()=>{
console.log('eat this: ', this)
console.log(this.name + '想吃饭了')
}
state = {
count:1
}
}
const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()
p1.eat()
p2.eat()
//console.log('say: ',p1.say === p2.say) // true say方法在 Perons.prototype.say
//console.log('eat: ',p1.eat === p2.eat)// false 每一个实例对象都有一个自己的eat方法
let obj = {
name:'ll',
f1:p1.say,
f2:p1.eat
}
// obj.f1() // 我的名字是: ll, 我的年龄是: undefined
// say 方法中的this,谁调用this指向谁。
obj.f2()
## 1-3. 注意事项
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<div class="box"></div>
</body>
<script>
{
// 1. 可以在同一个页面有多个容器
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('root容器')
// const box = ReactDOM.createRoot(document.querySelector('.box'))
// box.render('box 容器')
}
{
// 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('首次渲染')
// root.render('二次渲染')
}
{
// 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root1.render('root 一')
// root2.render('root 二')
}
{
// 4. 不能将 html body 指定为 react容器
// html
// const root = ReactDOM.createRoot(document.documentElement);
// root.render('html')
//
// const root = ReactDOM.createRoot(document.body);
// root.render('body')
}
{
// 5. 支持链式调用
ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
}
</script>
</html>
1-3. 虚拟DOM和真实DOM
<script>
/**
* react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
* 1. 页面是如何渲染的?
* 地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
* 1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
* 1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
* 1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
*
* 2. 什么是重排和重绘?
* css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
*
* 3. 传统DOM操作的缺陷!
* 频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
* this.style.height = 100px;
* this.style.left = '200px'
*
* 4. React、vue 性能更好,为什么?
* React 和 vue引入了虚拟dom 优化性能
*
* 5. 虚拟DOM是什么?
* 1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
* 2. 虚拟DOM 通过 render 可以转化为真实DOM
* 3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
* 4. 虚拟dom 在react中,我们又称之为时 react元素
* 5. 虚拟dom的创建有两种方法
* 1. 通过 React.createElement() 创建
* 2. 通过 jsx 创建
*
*/
</script>
1-4. call apply bind区别
call apply 立即执行
bind:返回一个新函数,不执行
let obj = {name:'atguigu'}
function getSonData(a){
console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])
// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()
1-5. 快速创建 类组件和函数组件的快捷键
rfc: react function component
rcc: react class component
1-6. class-普通方法-箭头函数方法
普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。
直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系
class Person{
constructor(name,age){
this.name = name;
this.age = age;
// this.eat = ()=>{
// console.log(this)
// console.log(this.name + '想吃饭了')
// }
// this.state = {
// count:1
// }
}
// say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
say(){
console.log('say this: ', this)
console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
}
// 通过=号赋值,定义箭头函数
// eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
eat = ()=>{
console.log('eat this: ', this)
console.log(this.name + '想吃饭了')
}
state = {
count:1
}
}
const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()
p1.eat()
p2.eat()
//console.log('say: ',p1.say === p2.say) // true say方法在 Perons.prototype.say
//console.log('eat: ',p1.eat === p2.eat)// false 每一个实例对象都有一个自己的eat方法
let obj = {
name:'ll',
f1:p1.say,
f2:p1.eat
}
// obj.f1()
// say 方法中的this,谁调用this指向谁。
obj.f2()