记录下react的基本语法及遇到的问题,分享给大家。让分享成为创作的动力!
react脚手架安装,组件定义,组件传值方式
学习文档链接 React中文官网
demo:https://gitee.com/wanglun945/project-react.git
一、使用
index.js
打包入口文件-- index.js 中使用root.render 渲染 引入的app.js。
app.js
通过import引入 image, scss,component.
import logo from './logo.svg';
import './App.css';
import Clock from "./component/clock"
import Profile from './component/profile';
import {useState} from "react";
function App() {
let [liStore, setLiStore] = useState([]);
let compName = {name: "zs"};
let changeName = name => {
compName = {name};
}
return (
<div className="App">
<section>
<Clock compName={compName} onChangeName={changeName}/>
<Profile liStore={liStore} onAdd={setLiStore}></Profile>
<img src={logo} alt="" srcset="" />
</section>
</div>
);
}
export default App;
组件
函数式组件 (推荐使用)
用来定义无状态、无副作用的UI组件,以函数的形式返回一个React元素。函数式组件通常使用函数参数来接收输入数据(props),并返回渲染的结果。易于编写和理解,并且在性能方面也有一定的优势。Class组件的语法相对复杂,代码量较多,而且在处理状态和生命周期时可能存在一些问题(如难以复用状态逻辑、容易产生性能问题等)
export default function Profile ({liStore, onAdd}) {
let onAddLi = () => {
let newL = [...liStore, {name: "11"}]
onAdd(newL)
}
return (
<>
<button onClick={() => onAddLi()}>+</button>
<ul>
{liStore.map((v, i) => {
return (<li style={{
lineHeight: "20px",
color: "#409fee"
}} key={i}>{i % 2 === 0 ? v.name : "66"}</li>)
})}
</ul>
</>
)
}
Class组件
拥有自身的状态(通过this.state来定义)和生命周期方法(如componentDidMount()、componentDidUpdate(), componentDidUpdate()、render()),可以处理用户交互和其他复杂的逻辑。
import React from "react";
class CompName extends React.Component {
constructor (props) {
super(props);
this.state = {}
}
shouldComponentUpdate (nextProps, nextState) {
console.log(nextProps, nextState);
return true;
}
componentDidMount () {
console.log("组件挂载")
}
render () {
return (
<>
<div>{this.props.name}</div>
<div>{this.props.age}</div>
</>
)
}
}
组件通讯
props
父组件通过 props 将数据传递给子组件。在父组件中定义属性并将其传递给子组件,子组件可以通过 this.props 来获取这些值。
eg:
父组件:
import Calculator from "./component/Calculator"
import BoilingVerdict from "./component/BoilingVerdict"
import { useState } from "react";
export default function Home () {
let [temperature, setTemperature] = useState(0)
let emitTemp = value => {
setTemperature(value)
}
return (
<>
<Calculator onChangeTemp={emitTemp} />
<BoilingVerdict temperature={temperature} />
</>
)
}
函数式组件
函数的第一个形参可接受父组件传递进来的属性,方法
export default function BoilingVerdict (props) {
if (props.temperature > 100) {
return (
<div>水沸腾了</div>
)
}
return (
<div>当前水温{props.temperature}</div>
)
}
class组件
子组件中使用super关键字。它用于继承父组件的属性方法。
import {Component} from "react";
export default class Calculator extends Component {
constructor (props) {
super(props);
this.state = {
temperature: 0
};
this.handleChangeValue = this.handleChangeValue.bind(this);
}
handleChangeValue (e) {
this.props.onChangeTemp(e.target.value);
this.setState({
temperature: e.target.value
})
}
render () {
return (
<>
<input value={this.state.temperature} onChange={this.handleChangeValue} />
</>
)
}
}
context
注意class组件需设置 contextType: 挂载在 class 上的 contextType 属性可以赋值为由 React.createContext() 创建的 Context 对象。此属性可以让你使用 this.context 来获取最近 Context 上的值。
1、context.js 文件,用于存放context数据
export const ThemesCtx = React.createContext(Themes.dark);
export const Themes = {
light: {
background: "#409fee",
},
dark: {
background: "#000",
}
}
2、父辈组件
ThemesCtx.Provider 提供的value使用state中的数据,防止Provider 重渲染子组件更新。
import F from "./f";
import Btn from "./btn"
import { ThemesCtx, Themes } from "../context/theme-ctx"; // 引入context
import React from "react";
function Comp () {
return (
<>
<span>custom</span>
</>
)
}
export default class P extends React.Component {
static contextType = ThemesCtx;
constructor (props) {
super(props);
this.state = {
theme: Themes.light
}
this.changeTheme = this.changeTheme.bind(this);
}
changeTheme () {
this.setState({
theme: Themes.dark
})
}
render () {
return (
<>
<ThemesCtx.Provider value={this.state.theme}>
<F>
<Btn handleChangeTheme={this.changeTheme} />
</F>
</ThemesCtx.Provider>
</>
)
}
}
孙级组件- class组件
import React from "react";
import { ThemesCtx } from "../context/theme-ctx";
export default class S extends React.Component {
static contextType = ThemesCtx;
render () {
return (
<div>
子级 {this.context.background}
</div>
)
}
}
子级组件- 函数式组件
- 使用 ThemesCtx.Consumer 组件获取context。
- 使用 useContext
import S from "./s";
import { ThemesCtx } from "../context/theme-ctx";
export default function F () {
const theme = useContext(ThemesCtx);
return (
<>
<ThemesCtx.Consumer>
{
val => (
<div>父集背景色: {val.background}</div>
)
}
</ThemesCtx.Consumer>
<div>useContext: {theme.background}</div>
<S />
</>
)
}
refs
用 React.forwardRef API 将 refs 转发到目标组件;接受一个渲染函数,其接收 props 和 ref 参数并返回一个 React 节点。目标组件接收ref,并设置ref属性值即可。外部组件使用 var targetRef = React.useRef() ;targetRef .current 可获取指向的DOM 节点。如果需要使用目标组件中的属性和方法,使用useImperativeHandle暴露对应的属性和方法接口,具体用法如下
父级组件
import React from "react";
import FancyBtn from "./photos/fancyBtn";
export default function Photos () {
const buttonRef = React.createRef();
let handlerClick = () => {
buttonRef.current.focus();
}
return <>
<FancyBtn ref={buttonRef} handlerClick={handlerClick}>Click</FancyBtn>
</>
}
目标组件
import React, { useImperativeHandle } from "react";
const FancyBtn = React.forwardRef((props, ref) => {
const inputRef = React.createRef(null);
let focusFn = () => {
inputRef.current.focus();
}
useImperativeHandle(ref, () => ({ // 暴露focus方法
focus: focusFn
}))
return (
<>
<button ref={ref} onClick={props.handlerClick}>
{props.children}
</button>
<input ref={inputRef} />
</>
)
})
export default FancyBtn
组件嵌套
使用children,可以是任何类型的内容,包括文本、元素、组件等
使用props传递自定义方法。
以下以class组件为例,函数式组件参照props的使用方式
1.父组件
import F from "./f";
import Btn from "./btn"
import React from "react";
function Comp () {
return (
<>
<span>custom</span>
</>
)
}
export default class P extends React.Component {
render () {
return (
<>
<F customComp={Comp}>
<Btn handleChangeTheme={this.changeTheme} />
</F>
</>
)
}
}
- 子组件
export default function F ({children, customComp}) {
return (
<>
<div>子组件:{children}</div>
<div>自定义组件:{customComp()}</div>
</>
)
}
受控组件和非受控组件
受控组件和非受控组件是两种处理表单元素的方式。下面是它们的简要说明:
受控组件(Controlled Components):受控组件是由React控制并管理其值的表单元素。通过使用state和事件处理函数,React可以实时更新和同步表单元素的值。当用户输入数据时,值会存储在React组件的状态中,并通过事件处理函数进行更新。这种方式提供了精确的控制和验证,适用于需要实时响应和验证用户输入的场景。
例如,一个受控的文本输入框的示例代码如下:
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
// 执行表单提交操作
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<button type="submit">提交</button>
</form>
);
}
}
非受控组件(Uncontrolled Components):非受控组件是由DOM元素本身保存和更新其值的表单元素。React中的非受控组件通过使用ref来访问DOM元素,并直接操作其值。与受控组件相比,非受控组件更加简单,适用于一些简单的表单场景。
以下是一个非受控的文本输入框的示例代码:
class MyForm extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
// 使用this.inputRef.current.value来获取输入框的值
// 执行表单提交操作
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.inputRef} />
<button type="submit">提交</button>
</form>
);
}
}
总结来说,受控组件需要在React组件内部管理其值,而非受控组件则由DOM元素自己管理其值。选择使用哪种方式取决于具体的需求和场景。
Hook
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
规则
- 只在最顶层使用 Hook( React 靠的是 Hook 调用的顺序。Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作)
- 只在 React 函数中调用 Hook
常用api
1. useState
允许在 React 函数组件中添加 state 的 Hook
2. useEffect
跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途
// 每次我们重新渲染,都会生成新的 effect;
// React 会在执行当前 effect 之前对上一个 effect 进行清除;
// 第二个参数是个数组,如果某些特定值在两次重渲染之间没有发生变化,可以通知 React 跳过对 effect 的调用;
// 确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量
useEffect(() => {
console.log(666); // componentDidMount 时执行。
return () => {
console.log(555); // 初始化时和componentDidUpdate 先执行 555 再执行 666 , componentWillUnmount 时执行。
}
}, [])
3. useReducer
state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化
import { useReducer } from "react";
// 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
export default function UseR ({initCount, initName}) {
const init = () => {
return {count: initCount, name: initName}
}
function reducer (state, action) {
switch (action.type) {
case "add":
return {count: state.count + 1, name: "add"};
case "minus":
return {count: state.count - 1, name: "minus"};
case "reset":
return init({initCount: action.initCount, initName: action.initName});
default:
throw new Error("**")
}
}
const [state, dispatch] = useReducer(reducer, {initCount, initName}, init);
return (
<>
<div>count: {state.count}</div>
<div>name: {state.name}</div>
<div>
<button onClick={() => {dispatch({type: "add"})}}>+1</button>
</div>
<div>
<button onClick={() => {dispatch({type: "minus"})}}>-1</button>
</div>
<div>
<button onClick={() => {dispatch({type: "reset", initCount, initName})}}>reset</button>
</div>
<div>
<button onClick={() => {dispatch({type: "1"})}}>Error</button>
</div>
</>
)
}
4. useCallback
用于缓存回调函数以提高性能。它接受两个参数:回调函数和依赖项数组。
当依赖项数组中的值发生变化时,React将重新创建回调函数。如果依赖项数组为空,则回调函数只会在组件首次渲染时创建一次。使用useCallback的常见场景是在将回调函数传递给子组件时,防止不必要的重新渲染。
import React, { useCallback } from 'react';
function MyComponent() {
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []);
return (
<button onClick={handleClick}>Click me</button>
);
}
在上面的例子中,handleClick回调函数将始终是相同的引用。只有当MyComponent组件首次渲染时,该函数才会被创建。这可以避免在每次重渲染时创建新的回调函数,并且可以提高性能。请注意,useCallback只在性能优化方面有用,通常在处理有性能问题的组件时使用它。如果组件没有性能问题,可以不使用useCallback。
5. useMemo
用于缓存计算结果以提高性能。它接受两个参数:计算函数和依赖项数组。
当依赖项数组中的值发生变化时,React将重新计算和缓存计算函数的结果。如果依赖项数组为空,则计算函数只会在组件首次渲染时执行一次。使用useMemo的常见场景是在渲染过程中执行昂贵的计算,并且这些计算的结果在依赖项未改变时保持不变。
下面是一个示例:
import React, { useMemo } from 'react';
function MyComponent() {
const expensiveResult = useMemo(() => {
// 执行昂贵的计算
console.log('Expensive computation');
return 2 + 2;
}, []);
return (
<div>{expensiveResult}</div>
);
}
在上面的例子中,expensiveResult的值将在组件首次渲染时计算,并被缓存起来。在后续的重渲染中,由于依赖项数组为空,计算函数将不再执行,而是直接返回缓存的结果。这样可以避免在每次重渲染时重复执行昂贵的计算,从而提高性能。请注意,useMemo应该谨慎使用,只在性能优化方面有需要时才使用。如果没有性能问题,可以不使用useMemo。此外,如果计算函数非常简单,那么使用useMemo可能会产生反效果,因为执行计算函数的开销可能大于直接计算结果的开销。
6. useRef
一个用于获取和操作 DOM 元素或其他可变值的 Hook。它的主要用法有两种:
(1)获取 DOM 元素的引用:通过 useRef 创建一个 ref 对象,然后将该 ref 对象赋值给组件中的某个 DOM 元素的 ref 属性。这样就可以通过 ref.current 来访问该 DOM 元素,以执行一些操作,比如获取或修改元素的属性、样式等。
示例代码如下:
import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef();
const handleClick = () => {
console.log(myRef.current); // 访问 DOM 元素
myRef.current.style.backgroundColor = 'red'; // 修改元素样式
};
return (
<div>
<button ref={myRef} onClick={handleClick}>
Click me
</button>
</div>
);
}
(2)存储可变值:useRef 也可以用于存储组件中的可变值,并且该值的修改不会触发组件重新渲染。这在某些情况下非常有用,比如在计时器、数据缓存等场景中。
import { useRef, useState, useCallback } from 'react';
export function Father () {
const [height, setHeight] = useState(0);
let sonRef = useCallback(node => {
if (node != null) {
let h = $(node).height();
setHeight(h)
}
}, [])
return (
<>
<Son sonRef={sonRef}></Son>
{height > 0 && <h2>h1 的高度 {height}</h2>}
</>
)
}
function Son ({sonRef}) {
let [show, setShow] = useState(false);
let handlerClick = () => {
setShow(true)
}
if (!show) {
return (
<button onClick={handlerClick}>显示H1标签</button>
)
}
return (
<h1 ref={sonRef}>h1</h1>
)
}
二、安装
检查本级是否安装node.js,没有安装的下载一个nvm,node包管理工具。不知道的小伙伴可以点击这里
执行命令 npm i create-react-app
执行命令 create-react-app hello-react
执行npm run start,项目运行起来后开始编写hellow react。对于spa应用来说,通过虚拟DOM树挂载在root节点上。ReactDOM树挂载在root节点下。ReactDOMRoot.js中提供createRoot()方法来创建ReactDOM树的Root节点,并且 render()方法使其渲染或更新。
三、遇到问题
-
Q: 初始化加载组件会渲染两次的问题;
A: 使用脚手架创建项目,默认会开启严格模式,在严格模式下,开发环境下会刻意执行两次渲染,用于突出显示潜在问题。在index.js 中注释 React.StrictMode 即可。
ps: 严格模式会检测过时的生命周期方法;检测不安全的生命周期方法;检测废弃的 context API 使用;检测副作用等 -
Q: 父组件修改props值,子组件不更新视图;
A: 在函数式组件中传递给子组件的值,用父组件state的值,使用setState修改,触发render方法更新视图。 -
Q: class组件声明了,而且使用了,但是终端提示 ‘logProps’ is defined but never used no-unused-vars。如图所示
A: 检查声明组件的变量名 首字母是否大写,改为大写即可。
-
Q: 使用 jquery 报错 Line 15:17: ‘$’ is not defined no-undef
A: 下载jquery包,再引入即可。 先执行 npm i jquery -D , 再在使用的文件中引入 import $ from “jquery”
-
Q: 使用sass报错
A: 下载 npm i sass sass-loader node-sass -D后,再执行npm run start 即可。