个人网站开(九)五系统前端react

前言

为什么要开始学react呢,感觉很大原因是因为本人最近拿到了团子的暑期实习offer,想着先熟悉熟悉技术栈,所以开始学习react了

正文

总之先开篇讲一下我的react学习方法,先大概看了B站的视频,然后就是去看网上的教程,最后就是直接开写,毕竟实践出真知,这个系统主要做的是文件传输,包括普通文件和音频文件和视频文件,还有他们的管理,所以写起来还是遇到了很多的问题的......

react适应

因为我原先没用过react,所以上手还是花了点时间的,不过在我大概写了几个页面之后我就差不多明白了,而且我现在一般只用函数式去写react组件,这样的话调用hooks实际上和vue3的相差无几,所以用起来也是比较熟练了,那么在这个基础上我觉得值得说的就是下面这几个点:

各种钩子函数(hooks)

React 的 Hooks 有很多,这里列出并说明一些主要的钩子函数:

1. **useState**: 它是最常用的 Hook,允许你在函数组件中添加 state。例如:

```javascript

const [count, setCount] = useState(0);

```

这个 Hook 接受初始 state 作为参数,返回一对值:当前 state 以及更新 state 的函数。

2. **useEffect**: 这个 Hook 可以让你执行副作用操作(数据获取,订阅,手动更改 DOM 等等)。它接受两个参数,一个是包含副作用逻辑的函数,一个是依赖数组。

```javascript useEffect(() => { // 这里是副作用逻辑

document.title = `You clicked ${count} times`; }, [count]); // 当 count 变化时执行副作用 ```

3. **useContext**: 这个 Hook 接受一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。就像这样:

```javascript

const ThemeContext = React.createContext('light');

const theme = useContext(ThemeContext); ```

4. **useReducer**: 这个 Hook 是 useState 的替代方案,接受类型 (state, action) => newState 的 reducer,并返回与 dispatcher 方法配对的当前 state。

```javascript

const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); //... } ```

5. **useCallback**: 返回一个记忆化版本的回调函数。 ```javascript const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], ); ```

6. **useMemo**: 返回一个记忆化的值。 ```javascript const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ```

7. **useRef**: 返回一个可改变的 ref 对象,其 .current 属性被初始化为传递的参数(initialValue)。返回的对象在组件的全生命周期内保持不变。 ```javascript const refContainer = useRef(initialValue); ```

8. **useImperativeHandle**: 用于让你在使用 ref 时自定义暴露给父组件的实例值。这样,父组件的“refs”就会指向某个特定的值,而不是子组件实例。 ```javascript useImperativeHandle(ref, createHandle, [deps]) ```

9. **useLayoutEffect**: 和 useEffect 相似,不过它发生在更早的时间──React 更新 DOM,浏览器更新视窗后,然后再执行这个 Hook。 ```javascript useLayoutEffect(() => { // 在浏览器绘制新的 UI 之后,但在屏幕的下一次绘制之前,synchronously }); ```

10. **useDebugValue**: 可以在 React 开发者工具中显示自定义 hook 的标签。 ```javascript useDebugValue(value) ``` 

context的使用

在 React 中,Context 提供了一种方式可以让数据在组件树中传递而不必一层一层手动传递 props。这在很多场景下都很有用,例如:

在应用中的很多组件需要用到同样一份数据,或者需要在深层嵌套的组件中传递数据。 使用 React Context 的基本步骤可以分为以下三步: 1. 创建上下文:使用 React.createContext() 方法可以创建一个 Context 对象。

```javascript

const MyContext = React.createContext(defaultValue);

```

2. 提供上下文值:在你的组件树中,用 Context.Provider 组件包裹起你需要将数据传递给其下所有的子组件的部分。

```javascript

<MyContext.Provider value={/* some value */}>

```

3. 消费上下文值:有两种方式可以在子组件中消费上下文的值。一种方式是用 Context.Consumer 组件。

```javascript

<MyContext.Consumer> {value => /* render something based on the context value */} </MyContext.Consumer>

```

另一种方式是用 useContext Hook。

```javascript

const value = useContext(MyContext);

```

以下是一个使用 Context 的完整例子:

```javascript

// 创建一个上下文并设置默认值为 'light'

const ThemeContext = React.createContext('light');

// 父组件

function App() { return (

// 使用 ThemeContext.Provider 为子组件提供值

<ThemeContext.Provider value="dark">

<Toolbar /> </ThemeContext.Provider> ); }

// 中间组件,即使这一层不用使用这个 context,也需要将它传递下去

function Toolbar() { return ( <div> <Button /> </div> ); }

// 子组件,这里使用 context function Button() { /

/ 使用 useContext Hook 消费 context

const theme = useContext(ThemeContext); return <button>{theme}</button>; }

```

在这个例子中,我们首先创建了一个 ThemeContext,并设置了默认值为 'light'。然后我们在 App 组件中用 ThemeContext.Provider 提供了一个 context 值为 'dark'。最后在 Button 组件中我们用 useContext Hook 消费了这个 context,将 Button 的内容设置为当前主题。如此我们就实现了跨组件传递数据,而无需通过 props 手动传递。

文件传输部分

文件传输

这个部分其实就是改写了一下组件的上传函数,具体代码如下:

import React, { useState } from "react";
import { InboxOutlined } from "@ant-design/icons";
import { message, Upload } from "antd";
import api from "../../api/document"; // 引入你写的api
import styles from './index.module.css'
const { Dragger } = Upload;

const App = () => {
  const [fileList, setFileList] = useState([]);

  const onChange = async ({ file, fileList }) => {
    setFileList(fileList);
    if (file.status === "uploading") {
    } else if (file.status === "done") {
    }
  };
  const handleUpload = async ({ file, onSuccess, onError }) => {
    const uid = localStorage.getItem("uid");

    const response = await api.sendDocument(uid, file);
    // 文件上传成功,调用api
    console.log("上传", file);
    if (response.error) {
      console.error("上传文件时发生错误:", response.error);
      message.error(`上传文件时发生错误:${response.error}`);
      onError(new Error(response.error));
    } else {
      message.success(`${file.name} 文件上传成功.`);
      onSuccess(null, response);
    }
  };
  const onDrop = (e) => {
    console.log("Dropped files", e.dataTransfer.files);
  };
  const onError = (error) => {
    console.error("上传文件时发生错误:", error);
    message.error(`上传文件时发生错误:${error}`);
  };
  return (
    <div className={styles.detile}>
    <Dragger
      name="file"
      multiple={true}
      fileList={fileList}
      onChange={onChange}
      onDrop={onDrop}
      onError={onError}
      action="#" // 从"/api/document"文件中找到这个URL,并填到这里来
      customRequest={handleUpload} 
    >
      <p className="ant-upload-drag-icon">
        <InboxOutlined />
      </p>
      <p className="ant-upload-text">点击或将文件拖拽到这个区域来上传</p>
      <p className="ant-upload-hint">
        支持单个或批量上传. 严禁上传公司数据及其他禁止的文件.
      </p>
    </Dragger>
    </div>
  );
};

export default App;

唯一值得一提的大概是具体的上传部分用了formdata来进行上传

const sendDocument = async (uid, file) => {
  let formData = new FormData();
  formData.append("uid", uid);
  formData.append("file", file);

  try {
    const response = await axios({
      method: "post",
      url: `${process.env.REACT_APP_API_URL}/document`,
      data: formData,
      headers: {
        "Content-Type": "multipart/form-data;charset=utf-8", //加上字符编码信息
      },
    });
    return response.data;
  } catch (error) {
    console.error("Error during API Call", error);
    return { error: error.message };
  }
};

`FormData` 是 Web API 的一个接口,它提供了一种表示表单数据(键值对)的方法,可以使用 XMLHttpRequest.send() 方法发送,同时它还可以让你通过 append() 方法来添加新的键值对到现有的 FormData 对象中。以下是关于 FormData 如何使用的一些基本指南。

**创建 FormData 对象** 创建一个新的 FormData 对象时,你可以选择是否传入一个 HTML `<form>` 元素作为参数。如果传入了,那么这个 FormData 对象会使用这个表单的当前键/值对进行填充。

```javascript

// 创建一个空的 FormData 对象

const formData = new FormData();

// 用表单元素填充 FormData 对象

const formElement = document.querySelector("form");

const formDataWithForm = new FormData(formElement); ```

**添加和读取数据** 可以用 `append()` 方法向 FormData 对象添加新的键值对,用 `get()` 方法来读取键值对。

```javascript

const formData = new FormData();

formData.append("username", "jinxu chen");

console.log(formData.get("username"));

// 输出 "jinxu chen" ```

**向服务器发送 FormData**

要向服务器发送 FormData,最常用的方法是通过 fetch API 或者 XMLHttpRequest。

```javascript

// 使用 fetch API

fetch("/api/endpoint", { method: "POST", body: formData });

// 使用 XMLHttpRequest

const xhr = new XMLHttpRequest();

xhr.open("POST", "/api/endpoint");

xhr.send(formData); ```

音频录制和上传

上传部分倒是老一套的formdata,值得一讲的大概是录制部分?这里其实也是调用web的接口,就像这样,核心函数如下:

const Sound = () => {
    const [recording, setRecording] = useState(false);
    const [audioURL, setAudioURL] = useState(null);
    const mediaRecorderRef = useRef(null);
    const audioChunksRef = useRef([]);
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    const seconds = String(now.getSeconds()).padStart(2, '0');
    const fileName = `${year}${month}${day}_${hours}${minutes}${seconds}.wav`;
    const startRecording = () => {
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then(stream => {
                mediaRecorderRef.current = new MediaRecorder(stream);
                audioChunksRef.current = [];

                mediaRecorderRef.current.addEventListener("dataavailable", event => {
                    console.log("dataavailable event has been triggered");
                    audioChunksRef.current.push(event.data);
                });
                mediaRecorderRef.current.start(10);
                setRecording(true);
            }).catch(err => {
                message.error('获取音频流失败');
                console.log(err);
            });
    };

    const stopRecording = () => {
        mediaRecorderRef.current.stop();
        setRecording(false);
        const audioBlob = new Blob(audioChunksRef.current);
        console.log("audio data", audioChunksRef.current, audioBlob)
        const audioURL = URL.createObjectURL(audioBlob);
        console.log("audio url", audioURL)
        setAudioURL(audioURL);
    };

视频录制和上传

这边的话其实和音频也是如出一辙,唯一可以提一下的大概是如何将视频的数据流实时展示:

其实很简单,就是把数据流放到video标签里就成

 <video ref={videoRef} autoPlay muted className={style.show}/>

结语

总之先别急,慢慢来就成

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值