React的技术文档

React 的技术文档

react 的函数式写法

可以使用函数式方法来实现组件间的嵌套和调用

/* 定义大组件 */
const App =()=>{
    /* 定义Top子组件 */
    const Top =()=>{
        return (
        /* 唯一根节点 */
        <div>
        Top
        </div>
        );
    }
    /* 定义Connent子组件 */
    const Connent =()=>{
        return (
        /* 唯一根节点 */
        <div>
        Connent
        </div>
        );
    }
    /* 定义Foot子组件 */
    const Foot =()=>{
        return (
        /* 唯一根节点 */
        <div>
        Foot
        </div>
        );
    }

        return (
        /* 唯一根节点 */
        <div>
            <Top></Top>
            <Connent></Connent>
            <Foot></Foot>
        </div>
        );
}
export default App;

在 index.js 中


import React from 'react';
// import ReactDOM from 'react-dom';
import App from './com_组件的嵌套';/* 引入刚刚的文件 */

import { createRoot } from 'react-dom/client';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App></App>);

展示结果

请添加图片描述

react 对列表数据的渲染

可以使用 map 方法对列表遍历渲染

/* 在 App外部定义 */
/* 评论列表 */
  const list = [
    {
      rpid: 3,
      // 用户信息
      user: {
        uid: 13258165,
        //评论内容
        content: "哎,不错哦",
        // 评论时间
        ctime: "10-18 08:15",
        like: 60,
      },
    },
    {
      rpid: 4,
      // 用户信息
      user: {
        uid: 13286165,
        //评论内容
        content: "666",
        // 评论时间
        ctime: "10-19 06:12",
        like: 50,
      },
    },
    {
      rpid: 5,
      // 用户信息
      user: {
        uid: 13258565,
        //评论内容
        content: "还不错",
        // 评论时间
        ctime: "10-19 09:22",
        like: 89,
      },
    },
  ];
  /* 当前用户 */
const currentUser = {
  uid: 13258165,
};
/* 切换最新最热 */
const tabs = [
  { type: "hot", text: "最热" },
  { type: "time", text: "最新" },
];
/*const App =()=>{} 省略代码 */
/* 在Connent子组件中 */
const Connent = () => {

  return (
    /* 唯一根节点 */
    <div>
      Connent
      <h1>实现评论列表渲染列表渲染</h1>
      <div>
        {list.map((item) => (
          <div key={item.rpid}>
            <div>用户名:{item.user.uid}</div>
            <div>评论内容:{item.user.content}</div>
            <span>点赞数量:{item.user.like}</span>
            <span>发布时间:{item.user.ctime}</span>
            <hr></hr>
          </div>
        ))}
      </div>
    </div>
  );
};

展示结果

在这里插入图片描述


删除用户评论

涉及到对数据进行增删改查的操作,需要使用到 React 的 useState

useState 是一个 React Hook,它允许你向组件添加一个 状态变量
useState 钩子需要写在 App 内,不能写在子组件当中,否则报错 React Hook “useState” cannot be called at the top level.
意思是只能从 React 函数组件或自定义钩子中调用 Hook

Hook 使用条件

只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层以及任何 return 之前使用 Hook

例如 对评论列表进行状态管理


 const App()=>{
    .....
     /* 维护评论列表 */
    const [userList, setList] = useState(list);
    const Connent=()=>{
       const [userList, setList] = useState(list);/* 错误,只能在最顶层使用Hook */
    }
    .....
 }

  /*list 数据的初始值
    userList 它将与你传递的 list 相匹配,接下来的操作就可以使用到userList来代替对原始list的操作
    setList 它可以让你将 userList 更新为不同的值并触发重新渲染
    当你赋新值时候,就才可以触发页面的更新
  */

通过条件渲染出删除按钮

item.user.uid 与 currentUser.uid 比较,相同就说明当前评论是 currentUser 发出,可以删除

const Connent = () => {
    return (
      /* 唯一根节点 */
      <div>
        Connent
        <h1>实现评论列表渲染列表渲染</h1>
        <div>
          {userList.map((item) => (
            <div key={item.rpid}>
              <div>
                用户名:{item.user.uid}
                {/* 获取到item.user.uid与currentUser.uid比较 第一种写法*/}
                {currentUser.uid === item.user.uid && (
                    {/* 此处样式需要驼峰命名 */}
                  <span style={{ paddingLeft: 90 }}>删除</span>
                )}
                {/* 第二种写法 */}
                 {/* {currentUser.uid === item.user.uid?(<span style={{ paddingLeft: 90 }}>删除</span>:''):("")} */}
              </div>
              <div>评论内容:{item.user.content}</div>
              <span>点赞数量:{item.user.like}</span>
              <span>发布时间:{item.user.ctime}</span>
              <hr></hr>
            </div>
          ))}
        </div>
      </div>
    );
  };

展示结果

绑定事件

react 中绑定事件,建议全部使用箭头函数来绑定,因为在使用 ES6 classes 或者纯函数时,react 不会自动绑定 this 到当前组件上,需要手动实现 this 的绑定。箭头函数可以自动绑定定义此函数作用的 this,因此不需要 bind。

const Connent = () => {
    .....
    /* 既想要自定义参数也想要e */
    const getSelfAndE = (name, e) => {
      console.log(name, e);
    };
    /* 只要自定义参数 */
    const getSelf = (name) => {
      console.log(name);
    };
    .....
    return I(
        <div>
            .....
            <h1>事件绑定</h1>
            <h3>点击事件绑定(既想要自定义参数也想要e)</h3>
            <button onClick={(e) => getSelfAndE("lls", e)}>点击获取</button>
            <h3>点击事件绑定只要自定义参数</h3>
            <button onClick={() => getSelf("lls")}>点击获取</button>
            .....
        </div>
    )


}

给删除按钮绑定事件

通过 filter 方法把符合条件为评论 id 和用户 id 不相同的留下

const Connent = () => {
    .....
     /* 定义删除方法 */
    const delComment = (userId) => {
      /* 修改评论内容,对评论内容过滤 使用之前定义的userList,修改状态值,把过滤好的数据重新渲染出来 */
      setList(userList.filter((item) => item.rpid !== userId));

    };
    .....

     return (
      /* 唯一根节点 */
      <div>
        Connent
        <h1>实现评论列表渲染列表渲染</h1>
        <div>
          {userList.map((item) => (
            <div key={item.rpid}>
              <div>
                用户名:{item.user.uid}
                {currentUser.uid === item.user.uid ? (
                  <span
                    style={{ paddingLeft: 90 }}
                    {/* 绑定删除事件 */}
                    onClick={() => {
                      delComment(item.rpid);
                    }}
                  >
                    删除
                  </span>
                ) : (
                  ""
                )}
              </div>
              <div>评论内容:{item.user.content}</div>
              <span>点赞数量:{item.user.like}</span>
              <span>发布时间:{item.user.ctime}</span>
              <hr></hr>
            </div>
          ))}
        </div>
        ........
      </div>
    );
}


展示结果

在这里插入图片描述

绑定样式

style 内联样式要写成对象形式,并使用驼峰命名,class 样式使用 className={‘样式名称’}(需要外部引入)

const Connent = () => {
    return (
        <div>
            .....
            <h1>绑定样式</h1>
            <span style={{ fontSize: 55 }}>内联样式</span>
            <span className={"tabsItemFont"}>外部引入</span>
            .....
        </div>
    )
}

展示结果

在这里插入图片描述

切换评论类型

设置评论类型初始状态
  /* 保留当前选择类型*/
  const [currentType, setTabs] = useState("hot");
遍历选项
<div>
    Connent
    <h1>实现评论列表渲染列表渲染</h1>
    <div>
        {tabs.map((item) => (
            <span
              key={item.type}
              style={{ paddingRight: 90 }}
            >
              {item.text}
            </span>
          ))}
            {userList.map((item) => ())}
    </div>
<div>
//外部css样式
.activeTabs{
    color: red;
}
.tabsItemFont{
    font-size: 25px;
    font-weight: 800;
}

展示结果

在这里插入图片描述

绑定样式和切换样式
const Connent = () => {
    /* 点击切换 */
    const handleChange = (type) => {
      /* 保存当前点击值 item.type */
      // console.log(type);
      setType(type);
    }
    return (
        <div>
            .....
           {tabs.map((item) => (
          <span
            key={item.type}
            onClick={() => {
              handleChange(item.type);
            }}
            style={{ paddingRight: 90 }}
            {/* 绑定样式 tabsItemFont固定class样式,往后加上不需要逗号
                当前点击的type和之前保留当前选择类型相同时候,才会赋值
            */}
             className={`tabsItemFont ${
               currentType === item.type && "activeTabs"
             }`}
          >
            {item.text}
          </span>
          .....
        ))}
        </div>
    )
}
使用 classnames 包管理样式

当要切换的样式较少时候可以使用 className={tabsItemFont ${currentType === item.type && "activeTabs"}}这样的模版字符串写法,但如果需要很多切换,则需要下载 npm 包,使用 classnames 来管理我们的样式

npm i classnames;

import classNames from "classnames";

//   className={`tabsItemFont ${
//     currentType === item.type && "activeTabs"
//   }`}
/* 将className切换为如下代码 */
className={classNames("tabsItemFont", {
activeTabs: currentType === item.type,
})}

展示结果

在这里插入图片描述

添加评论

原理与删除评论类似,改变 const [userList, setList] = useState(list);状态即可

react 中获取节点元素

通过在标签身上绑定 ref 值,即可绑定元素

方法一 使用 useRef

import {useRef } from "react";
const Connent = () => {
  .....
  /*方法一 使用useRef*/
    const spanRef = useRef(null);
    const inputRef = useRef(null);
  .....
  return (
    <div>
    ......
    <h1>获取节点元素</h1>
        <span ref={spanRef}>标签元素</span>
        <input type="text" ref={inputRef}></input>
        <button
          onClick={() => {
            getRef();
          }}
        >
          获取ref值
        </button>
    ......
    </div>
  )
}

方法二 使用 React.createRef()

import React from "react";
const Connent = () => {
  .....
   /* 方法二 使用 React.createRef(); */
    const spanRef = React.createRef();
    const inputRef = React.createRef();
  .....
  return (
    <div>
    ......
    <h1>获取节点元素</h1>
        <span ref={spanRef}>标签元素</span>
        <input type="text" ref={inputRef}></input>
        <button
          onClick={() => {
            getRef();
          }}
        >
          获取ref值
        </button>
    ......
    </div>
  )
}

展示结果

在这里插入图片描述

实现类似 Vue 的双向绑定

通过 input 的 onChange 属性获取每次输入框该变的值,触发输入值 inputValue 的状态管理函数更新值即可重新渲染到页面。

const Connent = () => {
  .....
  /* 实现类似Vue的双向绑定 */
  /* 保留 inputValue状态*/
    const [inputValue, setInputValue] = useState("双向绑定");
  /* 每次改变时候触发setInputValue,赋新的值*/
    const changInpt = (e) => {
      console.log(e.target.value);
      setInputValue(e.target.value);
    };
  .....
  retunrn (
    <div>
      .....
       <h1>实现类似Vue的双向绑定</h1>
        类似vue的双向绑定{inputValue}
      <input type="text" value={inputValue} onChange={(e) => changInpt(e)} />
      .....
    </div>
  )


}

展示结果

在这里插入图片描述

组件间通信

父子通信

父给子传参

定义 ConnentChild 组件,父亲为 Connent 组件,在调用子组件时候传达参数,子组件通过 props 来获取参数。
props 可传递任意的数据数字、字符串、布尔值、数组、对象、函数、JSX。
props 是只读对象子组件只能读取 props 中的数据,不能直接进行修改,父组件的数据只能由父组件修改


const App= ()=>{
  .....
  /* 定义ConnentChild子组件 */
  const ConnentChild = (props) => {
    console.log("我是子组件 收到父组件传来的信息", props.name);

    return (
      /* 唯一根节点 */
      <div>
        ConnentChild组件 "我是子组件 收到父组件传来的信息": {props.name}
      </div>
    );
  }

  /* 在Connent组件中调用ConnentChild子组件 */
  const Connent = () => {
    return (
      .....
       <h1>组件间通信</h1>
        <h2>父子间传参</h2>
        <h3>
          父给子传参
          {/* 在调用子组件时候传达name参数,值为"我是父亲" */}
          <ConnentChild name="我是父亲"></ConnentChild>
        </h3>
      .....

    )
  }
  .....
}

展示结果

在这里插入图片描述

子给父传参

父组件定义方法 const fun=(msg)={},将方法传递给子组件 onFun={fun},子组件调用 onFun 方法并且携带 msg 参数 onFun(msg),父亲就会收到参数

/* 父组件 */
const Connent =()=>{
  ......
  /* 父组件定义方法 */
  const getChildMsg = (msg) => {
      console.log("父亲收到", msg);
    };
  .....
  return (
    <div>
      .....
      <h3>
        子给父传参
        {/* 将方法传递给子组件 */}
        <ConnentChild onGet={getChildMsg} name="我是父亲"></ConnentChild>
      </h3>
      .....
    </div>
  )
}
/* 子组件 */
const ConnentChild = (props) => {
    return (
      /* 唯一根节点 */
      <div>
        ConnentChild组件 "我是子组件 收到父组件传来的信息": {props.name}
        {/* 绑定父组件传递过来的方法 */}
        <button
          onClick={() => {
            props.onGet("我是子,给父传参数");
          }}
        >
          发送
        </button>
      </div>
    );
  };

展示结果

在这里插入图片描述

兄弟间通信

通过状态提升的方法,即是兄弟先通过子传父方法将参数给父亲,再通过父传子的方法将参数给另一个兄弟

const App =()=>{
  /* 定义兄弟1 */
  const ConnentChild1 = (props) => {
    return (
      /* 唯一根节点 */
      <div>
        ConnentChild组件1
        <button
          onClick={() => {
            {/* 使用父亲传递的方法将参数带回 */}
            props.onGetToBro("我是兄弟,给兄弟传递参数");
          }}
        >
          发送
        </button>
      </div>
    );
  };
  /* 定义兄弟2 */
  const ConnentChild2 = (props) => {
    return (
      /* 唯一根节点 */
      <div>ConnentChild1组件的兄弟,我获取到了兄弟传递的参数,{props.msg}</div>
    );
  };

  const Connent = () => {
      ......
    /* 保留兄弟传递的参数 */
    const [childMsg, setChildMsg] = useState("");
    /* 定义兄弟传参给父亲方法 */
    const toBro = (msg) => {
      setChildMsg(msg);
    };
      ......
    }
    retunrn (
      <div>
        .....
         <h2>兄弟间传参</h2>
         {/* 兄弟1绑定子传父方法 */}
        <ConnentChild1 onGetToBro={toBro}></ConnentChild1>
        {/* 将参数传递给兄弟2 */}
        <ConnentChild2 msg={childMsg}></ConnentChild2>
        .....
      </div>

    )


}

展示结果

在这里插入图片描述

跨层通信

跨层通信就像祖孙通信一样,祖孙层级是相对而言,在祖先组件中定义的参数其后代都可以获取到传递参数。

// 实现步骤:
// 使用createContext方法创建一个上下文对象MsgContext,这个createContext的位置要在顶层,就是祖孙都可以访问的公共区域,不然写在祖先组件里面,孙子组件接收不到参数
// 在顶层组件(C)中通过 MsgContext.Provider 组件提供数据
// 在底层组件(B)中通过 useContext 钩子函数获取消费数据

import React, { useState, useRef, createContext, useContext } from "react";

const App = () => {
  /*  使用createContext方法创建一个上下文对象MsgContext */
  const MsgContext = createContext();
  /* 定义A组件 */
  const A = () => {
     const msg = useContext(MsgContext);
    return (
      <div>
        我是A组件嵌套在C里面,参数为{msg}
        <B></B>
      </div>
    );
  };

  /* 定义B组件 */
  const B = () => {
    /* 在底层组件(B)中通过 useContext 钩子函数获取消费数据 */
    const msg = useContext(MsgContext);
    return <div>我是B组件,嵌套在A里面,参数为{msg}</div>;
  };
  /* 定义C组件 */
  const C = () => {
    return (
      <div>
        我是C组件
        {/* 在顶层组件(C)中通过 MsgContext.Provider 组件提供数据 */}
        <MsgContext.Provider value="6666">
          <A></A>
        </MsgContext.Provider>
      </div>
    );
  };
  return (
    <div>
      <C></C>
    </div>
  );
};

export default App;


展示结果

在这里插入图片描述

useEffect

useEffect 是一个 React Hook 函数,用于在 React 组件中创建不是由事件引起而是由染本身引起的操作,比如发送 AJAX 请求,更改 DOM 等等。其实直接可以理解为在渲染完毕的生命周期时刻,和重新渲染的生命周期时刻,会被调用,理解为 Vue 的 mounted 生命周期函数就可以了。

useEffect 两个参数 useEffect(()=>{},[])

参数 1 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作

参数 2 是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。

/*  模拟发送网络请求*/
import React, { useEffect } from "react";
import axios from "axios"; /* 模拟配置好的网络请求 */
import { useState } from "react";
const App = () => {
  const [dataList, setdataList] = useState([]); /* 保留网络返回结果 */
  useEffect(() => {
    /* 发起网络请求 */
    async function getData() {
      let res = await axios("name");
      console.log(res.data);
      setdataList(res.data); /* 更改状态 */
    }
    getData();
  }, []);
  return <div>{dataList}</div>;
};

export default App;

useEffect 第二个参数

当第二个参数处于不同状态时候,函数执行时机有不同
当不赋予第二个参数时 触发时机 初始+组件更新
当第二个参数为空数组时 触发时机 初始
当第二个参数不为空数组时填入一个标识符,当标识符改变时候会再次触发 触发时机 初始+标识符改变


/*当useEffect 的第二个参数不同情况*/
import React, { useEffect, useState } from "react";

/* 当不赋予第二个参数时 触发时机 初始+组件更新 */
const App = () => {
  useEffect(() => {
    console.log("触发");
  });
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{{count}}</button>
    </div>
  );
};
/* 当第二个参数为空数组时 触发时机 初始 */
const App = () => {
  useEffect(() => {
    console.log("触发");
  },[]);
  // const [count, setCount] = useState(0);
  return (
    <div>
      {/* <button onClick={() => setCount(count + 1)}></button> */}
    </div>
  );
};
/* 当第二个参数不为空数组时填入一个标识符,当标识符改变时候会再次触发 触发时机 初始+标识符改变 */
const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("触发");
  }, [count]); /* 传入标识符count,当count变化他就会再次触发 */
  return (
    <div>
      <button onClick={() => setCount(count + 1)}></button>
    </div>
  );
};

export default App;

封装自定义 hook(网络请求)

创建封装自定义 hook.js

/*发送网络请求*/
import { useEffect, useState } from "react";
import axios from "axios"; /* 模拟配置好的网络请求 */
/* 第一个请求 */
export const useGetList = (name) => {
  const [dataList, setdataList] = useState({}); /* 保留网络返回结果 */

  useEffect(() => {
    /* 发起网络请求 */
    async function getData() {
      let res = await axios.get("https://api.xygeng.cn/one");
      console.log(res.data.data);
      if(res.code===200){
        setdataList(res.data.data); /* 更改状态 */
      }

    }
    console.log("请求一");
    getData();
  }, [
    name,
  ]); /* 传入改变标识符name,当name值改变时候,会再次触发useEffect重新请求 */

  return {
    dataList,
    setdataList,
  };
};
/* 第二个请求 */
export const useGetList2 = () => {
  const [dataList2, setdataList2] = useState({}); /* 保留网络返回结果 */
  useEffect(() => {
    /* 发起网络请求 */
    async function getData() {
      let res = await axios.get("https://api.quotable.io/random");
      console.log(res.data);
      if (res.status === 200) {
        setdataList2(res.data); /* 更改状态 */
      }
    }
    console.log("请求二");
    getData();
  }, []);

  return {
    dataList2,
    setdataList2,
  };
};


创建使用自定义hooks.js
import { useState } from "react";
import { useGetList, useGetList2 } from "./封装自定义hook";

const App = () => {


  const [name, setName] = useState(0);/* 设置标识符 */
  const { dataList } = useGetList(name);/* 引入第一个请求 */
  const { dataList2 } = useGetList2();/* 引入第二个请求 */
  const change = () => {/* 点击改变标识符 */
    setName(name + 1);
  };
  return (
    <div>
      {dataList.content}
      <br></br>
      {dataList2.content}
      <br></br>
      <button onClick={() => change()}>改变第一句</button>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

封装自定义 hook(组件)

封装组件引入外部函数形式

之前我们实现了评论列表的渲染,现如今将评论列表当为一个组件,为其进行封装
在封装自定义 hook.js 中将之前评论列表结构复制到定义 userListHook 里。

/* 封装评论列表 */
/* 此处传递的参数方法都可以使用*/
export const userListHook = (item, currentUser, delComment) => {
  return (
    <div key={item.rpid}>
      <div>
        用户名:{item.user.uid}
        {currentUser.uid === item.user.uid ? (
          <span
            style={{ paddingLeft: 90 }}
            onClick={() => {
              delComment(item.rpid);
            }}
          >
            删除
          </span>
        ) : (
          ""
        )}
      </div>
      <div>评论内容:{item.user.content}</div>
      <span>点赞数量:{item.user.like}</span>
      <span>发布时间:{item.user.ctime}</span>
      <hr></hr>
    </div>
  );
};

在之前的渲染评论列表的文件中,引入封装好的 userListHook

/* 在Connent中 */
const Connent = () => {

  ......
   /* 关于评论列表的数据和操作 */
   /* 评论列表 */
    const list = [
      {
        rpid: 3,
        // 用户信息
        user: {
          uid: 13258165,
          //评论内容
          content: "哎,不错哦",
          // 评论时间
          ctime: "10-18 08:15",
          like: 60,
        },
      },
      {
        rpid: 4,
        // 用户信息
        user: {
          uid: 13286165,
          //评论内容
          content: "666",
          // 评论时间
          ctime: "10-19 06:12",
          like: 50,
        },
      },
      {
        rpid: 5,
        // 用户信息
        user: {
          uid: 13258565,
          //评论内容
          content: "还不错",
          // 评论时间
          ctime: "10-19 09:22",
          like: 89,
        },
      },
    ];
   /* 当前用户 */
    const currentUser = {
        uid: 13258165,
    };
    /* 维护List */
    const [userList, setList] = useState(list);
   /* 定义删除评论方法 */
    const delComment = (userId) => {
      /* 修改评论内容,对评论内容过滤 */
      setList(userList.filter((item) => item.rpid !== userId));
      console.log(userId);
    };

  ......

    return (

      /* 在渲染评论列表的位置 将userListHook直接引入并传递参数和方法*/
      <div>
         ......
        {userList.map(
            (item) => userListHook(item, currentUser, delComment)
        )}
      </div>
        ......

    )


}

展示结果

在这里插入图片描述

封装组件在当前文件函数形式

当封装组件是在在当前文件进行封装时候,写法上有所不同,如定义的方法要通过子传父实现,调用参数需要解构。
在渲染评论列的文件中封装评论组件

/* 定义评论组件 */
/* 因为传递参数为props对象,需要解构出父传的参数item和方法onDel */
const UserListHook = ({ item, onDel }) => {
  return (
    <div>
      <div>
        用户名:{item.user.uid}
        {currentUser.uid === item.user.uid ? (
          <span
            style={{ paddingLeft: 90 }}
            onClick={() => {
              onDel(item.rpid);
            }}
          >
            删除
          </span>
        ) : (
          ""
        )}
      </div>
      <div>评论内容:{item.user.content}</div>
      <span>点赞数量:{item.user.like}</span>
      <span>发布时间:{item.user.ctime}</span>
      <hr></hr>
    </div>
  );
};

在 Connent 中使用 UserListHook

const Connent=()=>{
  ......
  return (
    <div>
      ......
       {userList.map(
            (item) => (
              //  userListHook(item, currentUser, delComment)
              <UserListHook item={item} onDel={delComment}></UserListHook>
            )
       )}
      ......
    </div>

  )
  ......
}

展示结果

在这里插入图片描述

以上都为个人总结,若有不对的地方请指正

Redux+发起请求渲染数据

定义小仓库 redux 的异步请求.js

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const axiosStore = createSlice({
  name: "axiosStore",
  initialState: {
    getData: "",
  },
  /* 定义改变方法 */
  reducers: {
    setgetData(state, action) {
      state.getData = action.payload;
    },
  },
});

/* 发起请求方法 */
/* 获取改变store值的方法 */
const { setgetData } = axiosStore.actions;
const sendReq = () => {
  return async (dispatch) => {
    const res = await axios.get("https://api.quotable.io/random");
    /* 派发改变store的方法 */
    console.log(res.data);
    if (res.status === 200) {
      dispatch(setgetData(res.data.content));
    }
  };
};
/* 将发起请求方法抛出 */
export { sendReq };
const reducer = axiosStore.reducer;
/* 将仓库抛出 */
export default reducer;

在大仓库中引入

/* 定义大仓库 */
import { configureStore } from "@reduxjs/toolkit";
/* 引入小仓库 */
import conuterReducer from "./modules/counterStore";
/* 引入发请求小仓库 */
import axiosReducer from "./modules/redux的异步请求";
/* 组合仓库 */
const store = configureStore({
  reducer: {
    counter: conuterReducer,
    axiosRed: axiosReducer,
  },
});
/* 导出大仓库 */
export default store;

渲染请求数据

import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { sendReq } from "./store/modules/redux的异步请求";

const App = () => {
  const dispatch = useDispatch();
  /* 获取仓库的值 */
  const { getData } = useSelector((state) => state.axiosRed);
  /* 初始化触发发请求 */
  useEffect(() => {
    dispatch(sendReq());
  }, [dispatch]);
  return <div>{getData}</div>;
};
export default App;

展示结果

在这里插入图片描述

React 的路由配置

创建路由

在 src 文件夹下创建 page 文件夹放置页面
src 创建 Login 文件夹里的 index.js


/* 路由跳转要使用useNavigate */
const Login = () => {

  return (
    <div>
      Login


    </div>
  );
};
export default Login;

src 创建 Article 文件夹里的 index.js

const Article = () => {

  return (
    <div>
      Article
    </div>
  );
};
export default Article;

在 src 文件夹下创建 router 文件夹
创建 index.js 进行路由配置

import Login from "../page/Login";
import Article from "../page/Article";

/* 配置路由 */
/* 引入路由依赖 */

import { createBrowserRouter } from "react-router-dom";
/* 添加路由对象 */
const router = createBrowserRouter([
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/article",
    element: <Article />,
  },
]);
export default router;

在 index.js 文件挂载路由 使用 react-router-dom 中的 RouterProvider 进行挂载

import React from "react";
// import ReactDOM from 'react-dom';
import { createRoot } from "react-dom/client";
import router from "./router";
import { RouterProvider } from "react-router-dom";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(

  <RouterProvider router={router}></RouterProvider>
);

地址栏输入 login 和 article 显示如下结果

展示结果

在这里插入图片描述

在这里插入图片描述

路由跳转

在 react 路由跳转中,有两种跳转路由写法,一种是声明式写法,一种是命令式写法

声明式

使用 react-router-dom 中的 Link

/* 从login页面跳转到article页面 */
  import { Link } from "react-router-dom";/* 导入Link */
  const Login =()=>{
    return (
      <div>

        <Link to="/article">跳转文章声明式写法</Link>

      </div>
    )
  }

命令式

使用 react-router-dom 中的 useNavigate

/* 从login页面跳转到article页面 */
  import { Link, useNavigate } from "react-router-dom";/* 导入 useNavigate*/
  const navigate = useNavigate();/* 实例化useNavigate */
  const Login =()=>{
      return (
        <div>
          <Link to="/article">跳转文章声明式写法</Link>
          <button onClick={() => navigate("/article")}>
            跳转文章命令式写法
          </button>
        </div>
      )
    }

展示结果

在这里插入图片描述

路由跳转传参

常用两种传参方式,一种是searchParams传参,一种是params传参。

searchParams 传参

需要以问号拼接字符串方式

/* login页面上 */


/* 在跳转路径上拼接参数id name */
  <button onClick={() => navigate("/article?id=10000&name=lls")}>
    跳转文章命令式写法(searchParams传参)
  </button>

searchParams 获取传递的参数

获取参数要使用到 react-router-dom 中的useSearchParams,实例化后使用 get 方法获取参数。
在 article 页面

import { useSearchParams} from "react-router-dom";/* 导入useSearchParams */
const article =()=>{
    const [params] = useSearchParams();/* 实例化 */
    /* useSearchParams时返回的数组结构是为了方便解构获取结果 */
  //   /* searchParams传参数使用get方法获取参数 */
    const id = params.get("id");
    const name = params.get("name");
    return (
    <div>
      Article
      <br></br>
      searchParams传参数为id-{id},name={name}

    </div>
  );
}

展示结果

点击跳转按钮后
在这里插入图片描述

params 传参需要提前配置

params 传参需要提前在 router 文件夹的 index.js 配置好

import Login from "../page/Login";
import Article from "../page/Article";

/* 配置路由 */
/* 引入路由依赖 */

  import { createBrowserRouter } from "react-router-dom";

  const router = createBrowserRouter([
    {
      path: "/login",
      element: <Login />,
    },
    {
      /* searchParams传参 */
      // path: "/article",
      // element: <Article />,
      /* params传参提前占位 */
      path: "/article/:id/:name",
      element: <Article />,
    },
  ]);
  export default router;
params 传参

参数值按之前配置好顺序一样

import { Link, useNavigate } from "react-router-dom";
/* 路由跳转要使用useNavigate */
const Login = () => {
  const navigate = useNavigate();
  return (
    <div>
      Login
      <Link to="/article">跳转文章声明式写法</Link>
      {/* searchParams传参 */}
      <button onClick={() => navigate("/article?id=10000&name=lls")}>
        跳转文章命令式写法(searchParams传参)
      </button>
      {/* params传参 需要提前占位*/}
      <button onClick={() => navigate("/article/999/yzy")}>
      跳转文章命令式写法(params传参)
      </button>
    </div>
  );
};
export default Login;
params 接收参数

params 接收参数要使用到 react-router-dom 中的useParams

import { useSearchParams, useParams } from "react-router-dom";
/* 获取路由参数要使用 useSearchParams*/

  const Article = () => {

    //   /* searchParams传参数使用get方法获取参数 */
    const [params] = useSearchParams();
    const id = params.get("id");
    const name = params.get("name");


    /* params传参数获取参数 */
    const params2 = useParams();/* 实例化useParams */
    const id1 = params2.id;/* 获取路由参数 */
    const name1 = params2.name;/* 获取路由参数 */
    return (
      <div>
        Article
        <br></br>
        searchParams传参数为id-{id},name={name}
        <br></br>
        params传参数为id-{id1},name={name1}
      </div>
    );
  };
  export default Article;

展示结果

点击跳转按钮后
在这里插入图片描述

注意

这两种跳转方式刷新都不会丢失参数,但是如果在同一个路径上使用两种跳转传参方法会报错
如,useSearchParams 跳转是不需要提前配置路由的,但是使用 params 跳转配置好路由后,使用 useSearchParams 获取参数会失败。params 跳转配置好路由后,不传参也会报错。

配置二级路由

在 Article 文件夹下创建两个页面 article1.js 和 article2.js。

/* 在article1.js */
const article1 = () => {
  return <div>article1</div>;
};
export default article1;
/* 在article2.js */
const article2 = () => {
  return <div>article2</div>;
};
export default article2;

创建好后,在 router 文件夹里的 index.js 文件去路由配置子路由

/* 引入刚刚创建的页面 */
import Articlea from "../page/Article/article1";
import Articleb from "../page/Article/article2";
......
/* 在article中配置children */
{
    path: "/article/:id/:name",
    element: <Article />,
    children: [
      {
        path: "articlea",
        element: <Article1></Article1>,
      },
      {
        path: "articleb",
        element: <Article2></Article2>,
      },
    ],
  },

配置好路由后,在 article 配置路由出口,使用到 react-router-dom 中的Outlet

/* 在article页面 */
import {Outlet, Link } from "react-router-dom";/* 导入Outlet */
const Article = () => {
  return (
    ......
      <Link to="">1</Link>{/* 跳转到articlea */}
      <br></br>
      <Link to="articleb">2</Link>{/* 跳转到articleb*/}
      <Outlet></Outlet>{/* 配置出口 */}
    ......
  )
}

展示结果

点击跳转按钮后
在这里插入图片描述

点击 1
在这里插入图片描述

点击 2
在这里插入图片描述

配置默认路由

当我们点击跳转到 article 页面时候,子路由还没有显示,这时候就需要我们设置一个默认路由,当点击跳转 article 页面时候就显示 articlea 页面。

设置 index(默认路由标志)
{
/* Articlea路由设置index和将path设置为"" */
    path: "/article/:id/:name",
    element: <Article />,
    children: [
      {
        index: true,
        path: "",
        element: <Articlea></Articlea>,
      },
      {
        path: "articleb",
        element: <Articleb></Articleb>,
      },
    ],
  },


  /* 将Link to设置为"" */
   <Link to="">1</Link>

   /* 即可实现默认路由 */

路由懒加载

通过 lazy 函数进行导入组件,使用 Suspense 组件包裹 element 对应组件

import { Suspense, lazy } from "react";
// import Login from "../page/Login";
// import Article from "../page/Article";
/* 将上面导入改写成一下形式,配置路由懒加载 */
const Login = lazy(() => import("../page/Login"));
const Article = lazy(() => import("../page/Article"));
/* 配置路由 */
/* 引入路由依赖 */

import { createBrowserRouter } from "react-router-dom";
/* 添加路由对象 */
const router = createBrowserRouter([
  {
    path: "/login",
    // element: <Login />, fallback中可以传入组件<div>Loading...</div>这样
    element: (
      <Suspense fallback={"加载中"}>
        <Login />
      </Suspense>
    ),
  },
  {
    path: "/article",
    // element: <Article />,
     element: (
      <Suspense fallback={"加载中"}>
        <Article />
      </Suspense>
    ),
  },
]);
export default router;

封装 Token 权限控制路由

定义一个控制路由组件,把需要做权限判断的组件包裹起来,将包裹起来的组件当作参数传递给控制路由组件判断跳转。

封装判断组件

import { Navigate } from "react-router-dom";
export const AuthRoute = ({ route }) => {
  const token = localStorage.getItem("token");
  if (token) {
    /* 当存在token是,可以跳转 */
    return <>{route}</>;
  } else {
    /* 不存在token就重定向回去登录 */
    return <Navigate to={"/login"} replace />;
  }
};

引用判断组件对路由进行鉴权

import { Suspense, lazy } from "react";
import { createBrowserRouter } from "react-router-dom";

/* 导入控制跳转组件 */
import { AuthRoute } from "../封装判断路由组件";

const Login = lazy(() => import("../page/Login"));
const Article = lazy(() => import("../page/Article"));

const router = createBrowserRouter([
  {
    path: "/login",
    element: (
      <Suspense fallback={"加载中"}>
        <Login />
      </Suspense>
    ),
  },
  {
    path: "/article",
     element: (
      /* 使用判断组件 */
     <AuthRoute>
        <Suspense fallback={"加载中"}>
          <Article />
        </Suspense>
      </AuthRoute>
    ),
  },
]);
export default router;

useMemo

useMemo 相当于 Vue 的计算属性,但是 useMemo 不会改变原来的值,当所依赖的值发生变化时候才会触发 useMemo

import { useMemo } from "react";

const App = () => {
  const a = 1;
  /* useMeno计算api */
  const b = useMemo(() => {
    return a + 7 * 10;
  }, [a]);/* 数组里面写上依赖的项 */
  return (
    <div>
      原来的a:{a},计算后的a:{b}
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

useLocation

useLocation 获取当前路径,在 react-router-dom 中引入

import { useLocation } from "react-router-dom";
/* 获取当前路径 */
  const path = useLocation();/* 实例化 useLocation*/

  const getCurrentPath = () => {
    console.log(path.pathname);/* 通过pathname属性获取路径 */
  };
const App = () => {
  const a = 1;
  /* useMeno计算api */
  const b = useMemo(() => {
    return a + 7 * 10;
  }, [a]);/* 数组里面写上依赖的项 */
  return (
    <div>
      <button
        onClick={() => {
          getCurrentPath();
        }}
      >
        获取当前路径
      </button>
    </div>
  );
};
export default App;

展示结果

当前路径为
在这里插入图片描述

打印路径为
在这里插入图片描述

useReducer

useReducer 和 useState 的作用类似,用来管理相对复杂的状态,useState 能做到的事,它都能做到,甚至做得更好。
第一个参数 reducer 是函数 (state, action) => newState,接受当前的 state 和操作行为。
第二个参数 initialArg 是状态初始值。感觉形式上像 redux 管理数据一样。

import { useReducer } from "react";

/* 1定义一个reducer函数 根据不同的action返回不同的状态 */
const reducer = (state, action) => {
  switch (action.type) {
    case "INC":
      return state + 1;
    case "DEC":
      return state - 1;
    case "SET":
      return action.payload;/* 直接改变参数值action.payload,前提要传入payload */
    default:
      return state;
  }
};

const App = () => {
  /* 2组件内调用useReducer,传入刚刚定义的reducer函数和初始值 */
  const [state, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      当前state:{state}
      {/* 4更改state值 */}
      <button
        onClick={() => {
          dispatch({ type: "INC" });
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          dispatch({ type: "DEC" });
        }}
      >
        -
      </button>
      <button
        onClick={() => {
          dispatch({ type: "SET", payload: 1000 });{/* 传入payload */}
        }}
      >
        更新到1000
      </button>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

React.memo(useMeno,useCallback)

memo 内置函数,一种优化方法,可以在更新父组件时候不触发子组件的更新,当父组件传入的 props 发生变化时候,子组件才会更新。

当不加 memo 时候

import { useState, memo } from "react";

const Son = () => {
  console.log("子组件更新了");

};
/* 只有props发生变化时候他才会更新 */
const App = () => {
  const [num, setNum] = useState(0);
  return (
    <div>
      {num}
      <button
        onClick={() => {
          setNum(num + 1);
        }}
      >
        点击加一
      </button>
      <Son></Son>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

当加上 memo 时候

即使父组件的 num 发生变化时候,子组件也不会触发更新。

import { useState, memo } from "react";
/* 使用memo缓存组件 */
const Son = memo(() => {
  console.log("子组件更新了");

});
/* 只有props发生变化时候他才会更新 */
const App = () => {
  const [num, setNum] = useState(0);
  return (
    <div>
      {num}
      <button
        onClick={() => {
          setNum(num + 1);
        }}
      >
        点击加一
      </button>
      <Son></Son>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

当 props 的类型引发不同结果

1,传递一个简单类型的 prop,prop 变化时组件重新渲染,prop 值不变时候就不会重新渲染。

/* 简单类型 */
const num=100

2,传递一个引用类型的 prop,比较的是新值和旧值的引用是否相等当父组件的函数重新执行时,实际上形成的是新的数组引用,即使 prop 值不变化也会重新渲染。

/* 引用类型 */
const num=[12,45,3]

3.使用 useMemo 缓存一个引用类型就可以解决 prop 值不变化也会重新渲染问题。

/* 引用类型 */
const num=useMeno(()=>{
  return [12,45,3]
},[])

4.使用 useCallback 缓存一个函数,多用于子给父传参时候

没有使用 useCallback

import { useState, memo } from "react";
/* 使用memo缓存组件 */
const Son = memo((props) => {
  console.log("子组件更新了");

  return (
    <div>
      {/* 传给父亲 */}
      {props.ToSon(666)}
    </div>
  );
});
/* 只有props发生变化时候他才会更新 */
const App = () => {
  const [num, setNum] = useState(0);
  /* 定义父给子方法 */
  const toSon = (msg) => {
    console.log("子给父传来", msg);
  };
  return (
    <div>
      {num}
      <button
        onClick={() => {
          setNum(num + 1);
        }}
      >
        点击加一
      </button>
      <Son ToSon={toSon}></Son>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

使用 useCallback

即使父组件发生更新,但是由于函数被缓存下来,只是执行了初始化渲染,后续再没有因为父组件更新而触发了。

import { useState, memo,useCallback} from "react";
/* 使用memo缓存组件 */
const Son = memo((props) => {
  console.log("子组件更新了");

  return (
    <div>
      {/* 传给父亲 */}
      {props.ToSon(666)}
    </div>
  );
});
/* 只有props发生变化时候他才会更新 */
const App = () => {
  const [num, setNum] = useState(0);
  /* 定义父给子方法 */
  const toSon = useCallback((msg) => {
    console.log("子给父传来", msg);
  },[]);
  return (
    <div>
      {num}
      <button
        onClick={() => {
          setNum(num + 1);
        }}
      >
        点击加一
      </button>
      <Son ToSon={toSon}></Son>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

React 的 forwardRef

将子组件的 dom 元素抛出,父组件获取子组件的 dom 元素,一个或者多个。

import { forwardRef, useRef } from "react";
const Son = forwardRef((props, ref) => {
  /* 将传进去的ref解构出来使用 */
  const { sonRef, sonRef2 } = ref;
  /* 当只有一个ref时候不用解构直接使用ref参数 */
  return (
    <div>
      {/* 绑定ref */}
      <input type="text" ref={sonRef}></input>
      <div ref={sonRef2}>77777</div>

      {/* 当只有一个ref时候
        <input type="text" ref={ref}></input>
      */}

    </div>
  );
});
const App = () => {
  /* 获取input的 */
  const sonRef = useRef(null);
  /* 获取div的 */
  const sonRef2 = useRef(null);
  const showSonRef = () => {
    console.log(sonRef);
    console.log(sonRef2.current.innerHTML);
  };
  return (
    <div>
      {/* 传入ref */}
      <Son ref={{ sonRef, sonRef2 }}></Son>
      {/* 当只有一个ref时候
      <Son ref={sonRef}></Son>
       */}
      <button
        onClick={() => {
          showSonRef();
        }}
      >
        获取子ref
      </button>
    </div>
  );
};
export default App;


展示结果

在这里插入图片描述

React 的 useImperativeHandle

将子组件操作 dom 元素方法抛出给父元素,要结合 forwardRef 使用。

import { forwardRef, useImperativeHandle, useRef } from "react";
const Son = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  /* 控制聚焦 */
  const handlerFocus = () => {
    inputRef.current.focus();
  };
  useImperativeHandle(ref, () => {
    /* 此处的ref是父组件传来的sonRef */

    /* 将聚焦函数抛出 */
    return {
      handlerFocus,
    };
  });
  return (
    <div>
      <input type="text" ref={inputRef}></input>
    </div>
  );
});
const App = () => {
  const sonRef = useRef(null);

  const showSonRef = () => {
    sonRef.current.handlerFocus();
  };
  return (
    <div>
      <Son ref={sonRef}></Son>
      <button
        onClick={() => {
          showSonRef();
        }}
      >
        聚焦
      </button>
    </div>
  );
};
export default App;

React 的 zustand(Redux 的简洁版)

zustand 是 Redux 的简洁版,初学 Redux 真的有点反人类,配置太麻烦了。

建立大仓库和小仓库

新建store文件夹下的 index.js 文件,再在store文件夹下新建modules文件夹存放小仓库,在modules文件夹下新建两个文件夹
zustand 的使用 1.jszustand 的使用 2.js
index.js

import { create } from "zustand";
/* 引入小仓库1和2 */
import { smallStore1 } from "./modules/zustand的使用1";
import { smallStore2 } from "./modules/zustand的使用2";
const useStore = create((...a) => {
  return {
    ...smallStore1(...a),
    ...smallStore2(...a),
  };
});
// /* 导出大仓库 */
export default useStore;

zustand 的使用 1.js

export const smallStore1 = (set) => {
  return {
    /* 定义初始值 */
    count: 0,
    /* 定义修改方法 */
    inc: () => {
      /* 使用老数据写法 */
      set((state) => ({ count: state.count + 1 }));
      /* 不需要老数据就直接写成对象形式
          set({count:100})
          */
    },
  };
};

zustand 的使用 2.js,尝试配合请求一起使用

import axios from "axios";
export const smallStore2 = (set) => {
  return {
    /* 定义初始值 */
    list: {},
    /* 定义修改方法 */
    getList: async function getData() {
      let res = await axios.get("https://api.xygeng.cn/one");
      console.log(res);
      if (res.status === 200) {
        set({ list: res.data.data }); /* 更改状态 */
      }
    },
  };
};

配置好仓库后就去使用

import { useEffect, useState } from "react";
import useStore from "./store";
const App = () => {
  /* 将仓库的值和方法解构出来 */
  const { count, inc, list, getList } = useStore();
/* 初始化发请求 */
  useEffect(() => {
    getList();
  }, [getList]);
  return (
    <div>
      <br></br>
      {list.content}
      {count}
      <button onClick={() => { inc();}}>
        加
      </button>
    </div>
  );
};
export default App;

展示结果

在这里插入图片描述

以上是个人学习React的总结,如果有不对的对方请指正!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值