day:3
react中的插槽
react没有插槽这个概念,因为react本省足够灵活,所以通过一定的方式可以实现和vue中插槽同样的效果。
方式一:通过children实现插槽效果:
app.jsx中:
import React from "react";
import NavBar from './07_组件的插槽实现/NavBar'
class App extends React.Component{
render(){
return(
<div>
{/* NavBar组件内这部分代码会被当作children传递给子组件,子组件内部通过this.props.children接收
如果是多标签的化children会是一个数组,如果只有一个标签的化默认是一个值 */}
<NavBar >
<button>按钮</button>
<h2>哈哈哈</h2>
<i>标签</i>
</NavBar>
</div>
)
}
}
export default App
NavBar组件中的代码:
import React from "react";
import PropTypes from 'react'
import './style.css'
class NavBar extends React.Component{
render(){
const {children}=this.props
console.log(children)
return (
<div className="navbar">
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
)
}
}
// 控制插入navBar中的children的类型
NavBar.propTypes={
children:PropTypes.element
}
export default NavBar
navbar对应的css:
.navbar{
display: flex;
text-align: center;
height: 40px;
line-height: 40px;
font-size: 16px;
}
.h1,h2{
padding: 0px;
margin: 0px;
}
.left,.right{
width: 80px;
background-color: red;
}
.center{
flex:1;
background-color: #80b51d;
}
以上实现方法是通过:
1.首先App.jsx在组件标签内写入的标签元素会被当作子组件的children
2.NavBar中会通过this.props结构复制children,如果是多个标签,则默认是数组,如果只有一个元素,则不是数组,只有一个元素。
实现vue中插槽的效果。
方式2:通过props实现插槽效果
{/* 通过props实现插槽 */}
{/* 传递的数组通过绑定属性的方式,传递给组件,组件内部通过props接收 */}
<NavBar2 left={a} center={b} right={c}>
</NavBar2>
class NavBar extends React.Component{
render(){
const {left,center,right}=this.props
return (
<div className="navbar">
<div className="left">{left}</div>
<div className="center">{center}</div>
<div className="right">{right}</div>
</div>
)
}
}
// 控制插入navBar中的children的类型
// NavBar.propTypes={
// children:PropTypes.element
// }
export default NavBar
注意:这里的css文件是全局的,相同的类会混合生效。
作用域插槽:
例如展示什么标签是在父组件中控制,子组件控制显示的内容,就会用到作用域插槽,本质是通过绑定一个箭头函数,共享父组件中的标签和子组件中的显示内容。
具体后续补充......
非父子组件通信-Contex介绍和Sprea
Contex:上下文的意思。
作用:实现非父子组件之间的数据共享
实现步骤:
step:1 首先在文件目录下新建一个contex文件夹-ThemeContext.js文件,创建上下文。
import React from 'react'
const ThemeContext=React.createContext()
export default ThemeContext
step:2 共享数据的组件中引入ThemeContext ,通过ThemeContext.Provider包裹,value属性传数据。(这里只能是value,不能是其他字段,是固定好的)
import React, { Component } from 'react' import Home from './Home' import ThemeContext from './contex/them-contex' export class App extends Component { render() { return ( <div> <ThemeContext.Provider value={{color:'red',size:'30'}}> <Home></Home> </ThemeContext.Provider> </div> ) } } export default App
step:3 使用数据的组件中引入ThemeContext ,并通过HomeBanner.contextType=ThemeContext指定使用的上下文是ThemeContex上下文。到这一步就可以通过this.contex访问上下文中的数据。
import React, { Component } from 'react'
import ThemeContext from './contex/them-contex'
export class HomeBanner extends Component {
render() {
console.log('context',this.context)
return (
<div>
<h2>homeBanner:{this.context.color}</h2>
</div>
)
}
}
HomeBanner.contextType=ThemeContext
export default HomeBanner
函数式组件中使用context共享的数据:用Consumer
import React from 'react'
import ThemeContext from './contex/them-contex'
function HomeInfo() {
return (
<div>
<span>HomeBanner</span>
{/* 函数式组件中使用context共享的数据 */}
<ThemeContext.Consumer>
{
value=>{
return <h2>{value.color}</h2>
}
}
</ThemeContext.Consumer>
</div>
)
}
export default HomeInfo
day:4
一、useState
useState的基本使用
是什么?useState是react的一个hook函数,允许向组件中添加一个状态变量,相当于vue中的一个data。也是数据驱动视图。
import { useState } from "react";
function App(){
const [count,setCount]=useState(0)
const handleClick=()=>{
setCount(count+1)
}
return (
<div>
<button onClick={handleClick}>{count}</button>
</div>
)
}
export default App
使用:
首先需要导入useState。
其次数组中第一个参数是自定义的状态变量,第二个参数是set函数用改修改状态变量。
关于状态不可变
状态不可变就是一旦初始化变化,那变化不能被修改,只能被替换。
import { useState } from "react";
function App(){
const [count,setCount]=useState(0)
const [form,setForm]=useState({name:'join'})
const handleClick=()=>{
setCount(count+1)
}
const changeForm=()=>{
// 错误写法:直接修改
// form.name='jack'
// 正确写法
setForm({
...form,
name:'jack'
})
}
return (
<div>
<button onClick={handleClick}>{count}</button>
<button onClick={changeForm}>{form.name}</button>
</div>
)
}
export default App
代码报错:
react-dom.development.js:13123 Uncaught Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
at throwOnInvalidObjectType (react-dom.development.js:13123:1)
排查是不支持对象作为react的child.
原因是jsx中直接渲染了form对象,应该修改为form.name(属性值)的方式:
<button onClick={changeForm}>{form}</button>
基础样式控制:
可以使用行内样式,将样式定义为一个变量,引用方式、或者className类的方式控制样式的修改
{/* 行内样式使用小驼峰的写法 */}
<button onClick={handleClick} style={{color:'red',fontSize:'20px'}}>{count}</button>
案例:渲染评论列表
import React from 'react' import {useState} from 'react' import avatar from './images/bozai.png' import './App.scss' /** * 评论列表的渲染和操作 * * 1. 根据状态渲染评论列表 * 2. 删除评论 */ // 评论列表数据 const List = [ { // 评论id rpid: 3, // 用户信息 user: { uid: '13258165', avatar: '', uname: '周杰伦', }, // 评论内容 content: '哎哟,不错哦', // 评论时间 ctime: '10-18 08:15', like: 88, }, { rpid: 2, user: { uid: '36080105', avatar: '', uname: '许嵩', }, content: '我寻你千百度 日出到迟暮', ctime: '11-13 11:29', like: 88, }, { rpid: 1, user: { uid: '30009257', avatar, uname: '黑马前端', }, content: '学前端就来黑马', ctime: '10-19 09:00', like: 66, }, ] // 当前登录用户信息 const user = { // 用户id uid: '30009257', // 用户头像 avatar, // 用户昵称 uname: '黑马前端', } /** * 导航 Tab 的渲染和操作 * * 1. 渲染导航 Tab 和高亮 * 2. 评论列表排序 * 最热 => 喜欢数量降序 * 最新 => 创建时间降序 */ // 导航 Tab 数组 // const tabs = [ // { type: 'hot', text: '最热' }, // { type: 'time', text: '最新' }, // ] const App = () => { // 初始化评论列表渲染数据 const [conmmentList,setCommentList]=useState(List) return ( <div className="app"> {/* 导航 Tab */} <div className="reply-navigation"> <ul className="nav-bar"> <li className="nav-title"> <span className="nav-title-text">评论</span> {/* 评论数量 */} <span className="total-reply">{10}</span> </li> <li className="nav-sort"> {/* 高亮类名: active */} <span className='nav-item'>最新</span> <span className='nav-item'>最热</span> </li> </ul> </div> <div className="reply-wrap"> {/* 发表评论 */} <div className="box-normal"> {/* 当前用户头像 */} <div className="reply-box-avatar"> <div className="bili-avatar"> <img className="bili-avatar-img" src={avatar} alt="用户头像" /> </div> </div> <div className="reply-box-wrap"> {/* 评论框 */} <textarea className="reply-box-textarea" placeholder="发一条友善的评论" /> {/* 发布按钮 */} <div className="reply-box-send"> <div className="send-text">发布</div> </div> </div> </div> {/* 评论列表 */} <div className="reply-list"> {/* 评论项 */} {conmmentList.map(item=>( <div className="reply-item" key={item.rpid}> {/* 头像 */} <div className="root-reply-avatar"> <div className="bili-avatar"> <img className="bili-avatar-img" alt="" src={item.user.avatar} /> </div> </div> <div className="content-wrap"> {/* 用户名 */} <div className="user-info"> <div className="user-name">{item.user.name}</div> </div> {/* 评论内容 */} <div className="root-reply"> <span className="reply-content">{item.content}</span> <div className="reply-info"> {/* 评论时间 */} <span className="reply-time">{'2023-11-11'}</span> {/* 评论数量 */} <span className="reply-time">点赞数:{item.like}</span> <span className="delete-btn"> 删除 </span> </div> </div> </div> </div> ) )} </div> </div> </div> ) } export default App
使用注意事项:
1.首先useState引入的时候注意使用{}引入,import {useState} from 'react'。这里引入方式错误报错,找了小一个小时。
2.html结构复杂的时候可以通过一个小括号括起来,作为整体方便阅读。
如: {conmmentList.map(item=>(放html结构)
3.初始化组件数据 const [conmmentList,setCommentList]=useState(List)需要放在组件内部。
实现评论删除
功能需求:只有用户自己发表的评论才会显示删除按钮,当点击删除的时候从展示列表删除评论。
{/* 只有用户本人才显示uid:{内部做条件判断} */}
{item.user.uid===user.uid&& <span className="delete-btn" onClick=
{()=>deleteItem(item.rpid)}>
删除
</span>}
注意:
这里条件判断控制显示元素是通过{判断条件&&控制的对象}实现的
点击删除事件是通过点击事件触发一个定义的箭头函数,箭头函数中传递item.rpid当前评论id,
而不是deleteItem()这种单独的一个事件名。当触发点击事件的时候会执行回调。
onClick={()=>deleteItem(item.rpid)}
function deleteItem(id){
console.log(id)
const conmmentList_new=conmmentList.filter(item=>item.rpid!==id)
setCommentList(conmmentList_new)
}
实现tab切换效果
点击后高亮显示(动态添加样式)
思路:首先定义一个type数据,添加默认显示的初始值,标签添加动态样式判断。通过标签的点击事件修改useState中的type值。控制高亮。
<li className="nav-sort">
{/* 高亮类名: active */}
{tabs.map(item=>(
<span className={`nav-item ${item.type===type&&'active'}`} key={item.type} onClick={()=>changeSelect(item.type)}>{item.text}</span>
))}
</li>
通过:className={`nav-item ${item.type===type&&'active'}`}动态控制类名
const [type,setType]=useState('hot')
function changeSelect(type){
console.log(type)
setType(type)
}
排序
Lodash 简介 | Lodash中文文档 | Lodash中文网
通过第三方库losh进行排序
第一步:安装:npm i --save lodash
第二步:引入:import _ from 'lodash'
第三步:使用
function changeSelect(type){
setType(type)
if(type==='hot'){
setCommentList(_.orderBy(conmmentList, 'like', 'desc'))
}else if(type==='time'){
setCommentList(_.orderBy(conmmentList, 'ctime', 'desc'))
}
}
classNames库优化类名控制(此处省略,后补)
二、react中获取dom元素
import { useRef } from 'react'
function App(){
const inputRef=useRef(null)
function showDom(){
console.dir(inputRef.current)
// console.dir可以打印dom元素包含的文件内容
}
return(
<div>
<input type="text" ref={inputRef}/>
<button onClick={showDom}>获取dom</button>
</div>
)
}
export default App
注意:这里使用的是react的一个hooks方式,useRef。需要注意这里的使用方法。
console.dir的作用。
三、react中的useEffect
useEffect是什么?官方解释是一个react Hook函数,用于在react中创建不是由事件引起而是由渲染本省引起的操作,比如发送AJax请求,更改Dom等等。
这里感觉可以理解为vue中的onMounted生命周期钩子,使用应该常见的就是页面初始化的时候接收数据,发送请求这些。
基本使用公式:
第一个参数是一个回调函数,第二个参数是一个数组,数组可以为空,可以影响第一个回调函数的执行次数。
import { useState,useEffect } from 'react'
function App(){
const [jsonList,setjsonList]=useState([])
const URL='http://geek.itheima.net/v1_0/channels'
useEffect(()=>{
// 额外的操作:获取频道列表
async function getList(){
const res=await fetch(URL)
const list=await res.json()
setjsonList(list.data.channels)
console.log(jsonList)
}
getList()
})
return(
<div>
{jsonList.map(item=>(
<h2 key={item.id}>{item.name}</h2>
))}
</div>
)
}
export default App
副作用函数(也就是useEffect钩子中的第一个函数参数)执行次数
清除副作用
由组件渲染本身引起的对接组件外部的操作,社区都叫副作用,比如组件卸载时清除effect中开启的定时器。
使用:
案例代码:
react Hooks使用规则:
1.只能在组件中或者其他自定义hook函数中调用
2.只能在组件的顶层调用,不能嵌套在if、for、其他函数中
优化需求:
通过接口获取评论列表json-server
通过json-server工具模拟接口服务,通过axios发送接口请求。是一个广泛使用的模拟接口请求的全段请求库。
使用:
step1:安装json-server: npm install -g json-server
step2:新建一个db.json文件,复制一段json模拟返回数据。
step3:新建一个db.json文件,复制一段json模拟返回数据。(报错中:File db.json not found)
四、Redux 集中状态管理工具
类似于Vue中的Vuex,可以独立于框架运行,作用:通过集中管理的方式管理应用的状态。
搭建环境:安装两个库
安装工具的作用:
目录设计
redux 感觉还是有点难理解........继续
了解纯函数:
react中所有的组件或者函数,都要求像一个纯函数一样保护他们的props不被变动。deducer也被要求是一个纯函数。(理解:纯函数大概就是确定的输入产生确定的输出,比如函数外部定义一个变量,函数内部使用,那一定不是纯函数。或者修改函数的时候是通过调用函数的方法,自行修改,而不是直接修改函数,那就会更接近是一个纯函数。)
redux中的三个核心概念:store,reducer,action
store中的index.js文件:
const {createStore}=require('redux')
// 初始化的数据
const initialState={
name:'why',
counter:100
}
// 定义reducer函数:纯函数(名称可以是别的名字,一般用reducer)
function reducer(state=initialState,action){
console.log('reducer',state,action)
// 这里返回的不应该是initialState,而应该是修改后的state
// 同时因为第一次时返回的state是undefined,因此state需要设置默认值
// return initialState
// 1.有数据更新的时候,返回一个新的state;
if(action.type==="change_name"){
// 浅拷贝的方式修改,不直接修改,保持reducer的纯函数
return{...state,name:action.name}
}
// 2.没有数据更新,那么返回之前的state
//
return state
}
// 创建的store
// store会自动去调用reducer,把数据放到store中
const store=createStore(reducer)
// 导出store
module.exports=store
组件内派发action修改store数据:
const store=require("./store")
// 修改store中的数据,必须action
const nameAction={type:"change_name",name:'kobe'}
// 派发action修改store数据
store.dispatch(nameAction)
console.log(store.getState())
redux使用的代码优化:
优化1:订阅数据变化
之前每次数据修改后都需要store.getstate(),订阅数据之后数据一旦发生改变,都会执行订阅函数,自动获取新数据。
const store=require("./store")
// 订阅数据,一旦数据发生变化,就会自动执行unsubscribe函数
const unsubscribe=store.subscribe(()=>{
console.log("订阅数据的变化",store.getState())
})
// 修改store中的数据,必须action
const nameAction={type:"change_name",name:'kobe'}
// 派发action修改store数据
store.dispatch(nameAction)
store.dispatch({type:"change_name",name:'lilei'})
// console.log(store.getState())
优化2:优化reducer函数中的条件判断
// 定义reducer函数:纯函数(名称可以是别的名字,一般用reducer)
function reducer(state=initialState,action){
// 一般state中的条件判断用switch
switch(action.type){
case "change_name":
return {...state,name:action.name}
case "add_number":
return {...state,counter:state.counter+action.number}
default:
return state
}
}
优化3:优化动态派发action
// store.dispatch({type:"change_name",name:'lilei'})
// store.dispatch({type:"change_name",name:'kobe'})
// store.dispatch({type:"change_name",name:'wangxiaohong'})
之前的派发,存在重复代码,可以提取一个函数
// 代码重复:优化actions
const changeNameAction=(name)=>({
type:'change_name',
name
})
store.dispatch(changeNameAction('lilei'))
store.dispatch(changeNameAction('kobe'))
store.dispatch(changeNameAction('wangxiaohong'))
优化4:将action动态函数抽离出来,当到单独的文件中,所有的组件使用的时候直接引入,不需要自定义。继续优化中......
抽离到store中一个单独的文件actionCreators.js中。
const changeNameAction=(name)=>({
type:'change_name',
name
})
const addCounteAction=(num)=>({
type:'and_count',
num
})
module.exports={
changeNameAction,
addCounteAction
}
使用时在组件内引入:
const {changeNameAction,addCounteAction} =require("./store/actionCreators")
优化5:type参数优化
现有还有一个问题:action函数中type是固定的,需要组件内部和store中保持一致
store中新建一个constant.js文件。
const ADD_NUMBER="add_number"
const CHANGE_NAME="change_name"
module.exports={
ADD_NUMBER,
CHANGE_NAME
}
actionCreator中引入:
const {ADD_NUMBER,CHANGE_NAME}=require("./constants")
const changeNameAction=(name)=>({
type:CHANGE_NAME,
name
})
const addCounteAction=(num)=>({
type:ADD_NUMBER,
num
})
module.exports={
changeNameAction,
addCounteAction
}
store index.js文件中引入:
const {createStore}=require('redux')
const {ADD_NUMBER,CHANGE_NAME}=require("./constants")
// 初始化的数据
const initialState={
name:'why',
counter:100
}
// 定义reducer函数:纯函数(名称可以是别的名字,一般用reducer)
function reducer(state=initialState,action){
// console.log(state,action)
// 一般state中的条件判断用switch
switch(action.type){
case CHANGE_NAME:
return {...state,name:action.name}
case ADD_NUMBER:
return {...state,counter:state.counter+action.num}
default:
return state
}
}
// 创建的store
// store会自动去调用reducer,把数据放到store中
const store=createStore(reducer)
// 导出store
module.exports=store
优化6:处于reducer中的代码会变复杂。将reducer抽离成为单独的reducer.js文件,在store的入口文件中引入:
const {ADD_NUMBER,CHANGE_NAME}=require("./constants")
// 初始化的数据
const initialState={
name:'why',
counter:100
}
// 定义reducer函数:纯函数(名称可以是别的名字,一般用reducer)
function reducer(state=initialState,action){
// console.log(state,action)
// 一般state中的条件判断用switch
switch(action.type){
case CHANGE_NAME:
return {...state,name:action.name}
case ADD_NUMBER:
return {...state,counter:state.counter+action.num}
default:
return state
}
}
module.exports={
reducer
}