react学习笔记

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
}

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值