海星吧的react学习:组件的基本使用。

海星吧 2023-3-2 5737

组件生命周期部分:参考链接:https://cloud.tencent.com/developer/article/1907198

囊括了react组件部分的使用方式,我会继续学react,有更新会开新帖。

这个比较长,可以耐心看完,毕竟我也花了一个下午到晚上的时间写,加油前端人。

可以看完后自己敲一敲,看看react的特性。

import React, {
	ChangeEvent,
	createRef,
	memo,
	useCallback,
	useContext,
	useEffect,
	useMemo,
	useState,
} from "react";
// 学习react需要对js有相当程度的理解。没有学会class这些还是多看看js教程。
const bindStyle = {
	color: "gray",
};
const mapLiData = [
	{
		id: "1",
		value: "第一个li",
	},
	{
		id: "2",
		value: "第二个li",
	},
];

const mapLiDataObj = {
	a: "aaa",
	b: "bbb",
};
interface ctxStrongInterface {
	counter: number;
	// increment: Function;
}
const BaseCtxStrong: ctxStrongInterface = {
	counter: 0,
	// increment: function () {
	// 	console.log("increment", this);
	// 	this.counter += 1;
	// 	console.log("increment", this.counter);
	// },
};
const CtxStore = React.createContext<ctxStrongInterface | null>(null);

// 创建函数式
function TestJSX__1() {
	return <div>函数式组件 1</div>;
}
const TestJSX__2 = () => {
	return <div>函数式组件 2</div>;
};

// 创建组件式
interface TestJSX__3PropsInterface {
	color: string;
}
interface TestJSX__3StateInterface {
	name: string;
	counter: number;
	bindClass: string;
	isError: boolean;
	inputValue: string;
}
class TestJSX__3 extends React.Component<TestJSX__3PropsInterface, TestJSX__3StateInterface> {
	// 组件内置 props,state,通过this调用
	/*
        内置方法
        setState(obj | (state,props)=> {...state}, callback(/无参数/)) 更改组件的 state,
		调用方式:
		// this.state.counter : 0 => 1
		1. this.setState((state, props)=> ({
			...state,
			count: 1
		}))
		2. this.state = { counter: 1, hasError: false };
		// 这个原型函数还有第二个参数,传入一个函数,
		// 这个函数会在组件更新后回调,也就是当前这个setState处理完数据,
		// 并且经历到 componentDidMount, componentDidUpdate 生命周期之后
        // 但是这个函数千万不能乱用,很可能会造成多次渲染,甚至崩溃。
	*/
	/*
        内置方法
		forceUpdate()
		作用:强制调用 render 函数重新渲染,
			会跳过  shouldComponentUpdate(),
			但其子组件会不会跳过。通常应该避免使用此方法。
		调用方式:
			this.forceUpdate()
	*/
	// 声明 state
	// state = {
	// 	name: "这是一个类组件",
	// 	counter: 0,
	// 	bindClass: "color-gray",
	// };

	// 声明静态props
	static defaultProps: { color: string };

	// inputRef: React.RefObject<HTMLInputElement | null> = createRef();
	inputRef = createRef<HTMLInputElement>();

	// 类组件的生命周期:
	// 挂载组件时:
	constructor(props: any) {
		// constructor 必须要有 super(),不然this无法指定当前组件。
		// 需要用到super传入props,不传的话,props就是undefined。
		super(props);
		// this.handleClickAddCounter_2.bind(this);

		// 也可以在这里声明this.state = {...}
		this.state = {
			name: "这是一个类组件",
			counter: 0,
			bindClass: "color-gray",
			isError: false,
			inputValue: "",
		};
		console.log(" 组件构造函数开始:constructor");
	}
	static getDerivedStateFromProps(props: any, state: any) {
		// 功能是:根据 props 的变化来更新 state。
		// 初始挂载及后续更新时都会被调用,
		console.log(" 组件挂载或更新:hook is getDerivedStateFromProps");
		return null;
	}
	// getDerivedStateFromProps 后调用 render

	// 挂载后运行
	componentDidMount() {
		// 【调用】:会在组件挂载后(插入 DOM 树中)被调用;
		// 【使用】:适合于 数据初始化操作、   网络请求获取数据操作 。
		// 【注意】:这里调用 setState(),会触发render(),请谨慎使用,容易导致性能问题。
		console.log(" 组件挂载后/组件数据操作后:hook is componentDidMount");
	}

	// 更新:
	// static getDerivedStateFromProps 后
	shouldComponentUpdate(nextProps: any, nextState: any): boolean {
		//nextProps, nextState
		// 此方法仅用于性能优化。返回true,表示组件需要重新渲染;返回false,表示跳过渲染,默认返回值为 true。

		// 首次渲染或使用 forceUpdate() 时不会调用。
		// state 或 props 改变时,shouldComponentUpdate() 会在渲染执行之前被调用。
		// 不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。
		console.log(" 组件是否重新渲染:hook is shouldComponentUpdate");
		return true;
	}
	// shouldComponentUpdate 后会调用 render

	// 在组件发生更改之前获取一些信息(譬如:滚动位置等),返回值将作为参数传递给 componentDidUpdate()
	getSnapshotBeforeUpdate(prevProps: any, prevState: any) {
		//prevProps, prevState
		console.log(" 组件是否更新:hook is getSnapshotBeforeUpdate");
		return null;
	}
	// 组件更新后会被调用,首次渲染不会执行此方法。
	componentDidUpdate(prevProps: any, prevState: any, snapshot: any) {
		// 可以执行一些自定义操作,譬如进行一些网络数据请求。
		// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
		// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
		//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
		console.log(" 组件更新后:hook is componentDidUpdate");
		// 可以调用  setState(),但是一定要用条件语句包裹 setState(),否则渲染会进入死循环,
		// 因为setState会触发render(),render()后又会调用 componentDidUpdate 。请谨慎使用。
		// 如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。
	}
	// 卸载:
	componentWillUnmount() {
		console.log(" 组件卸载时:hook is componentWillUnmount");

		// 组件卸载 处理副作用
		// 当组件从 DOM 中移除时(卸载及销毁之前)调用。
		// 在此方法中执行必要的清理操作,例如,清除 timer,
		// 取消网络请求或清除在 componentDidMount() 中创建的订阅等。
	}

	// 错误处理:
	static getDerivedStateFromError(error: any) {
		// 在渲染阶段,后代组件抛出错误后被调用。
		console.log(" 组件报错:hook is getDerivedStateFromError");
		console.log("error");
		console.error(error);
		return { isError: true };
		// render() {
		// 	if (this.state.hasError) {
		// 你可以渲染任何自定义的降级 UI
		// 		return <h1>Something went wrong.</h1>;
		// 	}
		// 	return this.props.children;
		// }
	}
	componentDidCatch(error: any, info: any) {
		console.log("组件报错时:hook is componentWillUnmount");
		console.log("info");
		console.log(info);

		this.setState({
			isError: true,
		});
	}

	// 渲染:
	// 渲染 render 函数是类组件内置且必须重现的函数,里面返回的jsx就是组件最终显示的dom
	render() {
		if (this.state.isError) return <div>component is error</div>;
		// throw new Error('我发生了错误');
		return (
			<>
				{/*
                    <Fragment>幽灵标签:可在幽灵标签内写多个根节点,避免render函数返回的节点只能有一个,从而导致多出原本不需要的dom节点</Fragment >
                    缩写写法为:<></>
                */}
				<h1>class 类组件</h1>
				<p>
					<span>绑定Ref</span>
					<input type="text" ref={this.inputRef} onChange={this.handleInputRefChange} />
					<br />
					<span>绑定Ref e.target.value: {this.state.inputValue}</span>
				</p>
				<p>
					<span> 绑定class</span> <br />
					<span className="bind-class-name">绑定class</span> <br />
					<span className={this.state.bindClass}>绑定class</span> <br />
				</p>
				<p>
					<span>绑定 style </span>
					<br />
					<span style={{ color: "yellow" }}>绑定 style</span> <br />
					<span style={{ color: this.props.color }}>绑定 style</span> <br />
					<span style={bindStyle}>绑定 style</span> <br />
					<span
						style={{
							...bindStyle,
							fontSize: "20px",
						}}
					>
						绑定 style
					</span>{" "}
					<br />
				</p>
				{/* 显示 state */}
				<p>{this.state.name}</p>
				<p>counter:{this.state.counter}</p>
				<button onClick={this.handleClickAddCounter_1}>add counter 1</button>
				{/* 组件内函数 未绑定this */}
				<button onClick={this.handleClickAddCounter_2}>add counter 2</button>
				{/* 箭头函数调用 */}
				<button onClick={() => this.handleClickAddCounter_2()}>add counter 2</button>
				{/* 调用时绑定this */}
				<button onClick={this.handleClickAddCounter_2.bind(this)}>add counter 2</button>
				{/* <p>
					{Object.keys(this.props).map((item: any, index: number) => (
						<span key={index}>{item}:{index}</span>
					))}
				</p> */}
				<ul>
					{mapLiData.map((item) => {
						return <li key={item.id}>item: {item.value}</li>;
					})}
				</ul>
				<ol>
					{Object.getOwnPropertyNames(mapLiDataObj).map((key: any, index: number) => {
						// tsx:这里要显示告诉ts key是属于谁的。
						const value = mapLiDataObj[key as keyof typeof mapLiDataObj];
						return (
							<li key={key}>
								item: {key}
								{" ==> "}
								{value}
								...
								{index}
							</li>
						);
					})}
				</ol>
				<div>
					<h1>父子上下文</h1>
					<CtxStore.Provider value={{ counter: this.state.counter }}>
						<TestJSX__3__1></TestJSX__3__1>
					</CtxStore.Provider>
				</div>
			</>
		);
	}
	// 箭头 函数
	// 不用显示绑定this
	handleClickAddCounter_1 = () => {
		console.log(this);
		this.setState({ counter: this.state.counter + 1 });
	};
	// function 函数
	// 需要在constructor中绑定this,
	// 或者调用的时候绑定: this.handleClickAddCounter_2().bind(this)
	// 或者用 箭头函数 调用:()=> this.handleClickAddCounter_2()
	handleClickAddCounter_2() {
		console.log(this);
		this.setState({ counter: this.state.counter + 2 });
	}
	handleInputRefChange = (e: ChangeEvent<HTMLInputElement>) => {
		console.log(e);

		this.setState({
			inputValue: e.target?.value,
		});
	};
}
TestJSX__3.defaultProps = {
	color: "red",
};

class TestJSX__3__1 extends React.Component<unknown, unknown> {
	render(): React.ReactNode {
		return (
			<CtxStore.Consumer>
				{(_CtxStore) => (
					<div>
						<p>子组件 3-1</p>
						<p>来自父组件的信息:{_CtxStore?.counter}</p>
					</div>
				)}
			</CtxStore.Consumer>
		);
	}
}

function TestJSX__4() {
	/*
		useState(anyValue)
		传入一个需要被响应式更新的值,返回一个数组  [state, setState:((state) => newState, callback)]
		state 就是 响应式的值, setState 就是更新这个值的函数
		useState 返回的 setState 和 类组件的 this.setState 具有相同的参数
		但是 setState 属于把数据覆盖了
		如果 useState 传入的是一个对象 :useState({ a: 1,b: 2})
		那么更改的时候要注意 ,更改属性 a 的时候,得把 b 的值也传进去
		比如: 需要 state.a += 1 的时候
		setState(state=> ({
			...state,
			a: state.a += 1
		}))
		结果是 state => { a: 2,b: 2 }
		如果是这样的
		setState(state=> ({
			a: state.a += 1
		}))
		结果是 state => { a: 2 }
		给你们一个实例吧
		function TestUseState() {
			const [state, setState] = useState({ a: 1, b: 2 });
			useEffect(() => {
				console.log(state);
			},[state]);
			return (
				<>
					<div>
						<p>a:{state.a}</p>
						<p>b:{state.b}</p>
					</div>
					<button
						onClick={() =>
							setState((state) => ({
								...state,
								a: (state.a += 1),
							}))
						}
					>
						正常赋值
					</button>
					<button
						onClick={() =>
							setState((state) => ({
								a: state.a += 1,
							}))
						}
					>
						错误赋值
					</button>
				</>
			)
		}
		当然数组同理
	*/
	const [counter, setCounter] = useState(2);
	/*
		useEffect(callback,array)
		副作用 useEffect 是一个功能很多的 hook
		第一个参数是 callback 函数,它会在组件挂载和每次数据的更新时触发
		返回一个函数,会在数据更新前触发
		第二个参数 array ,它里面传入需要被跟踪的响应式数据,
		只有被跟踪的数据更改时,它的callback才会触发,当然组件挂载时也是会被触发的
	*/
	useEffect(() => {
		console.log("组件挂载和每次所有数据更新时 都触发!");
	});
	useEffect(() => {
		console.log("组件挂载或数据更新时 触发!");
		return () => {
			console.log("组件卸载时 触发!");
		};
	}, []);
	useEffect(() => {
		console.log("组件挂载或 counter 更新时 触发!");
	}, [counter]);
	/*
		useMemo(callback,array) => anyValue | jsxVNode
		缓存 useMemo
		它内部传入一个 callback 函数,这个函数会在组件挂载的时候触发一次,
		当第二个参数传入 需要被跟踪的 响应式数据时,callback也会执行一次
		它需要返回一个值,这个值可以是一个数据,也可以是一个jsx表达式
	*/
	const counterDouble = useMemo(() => {
		// console.log('组件挂载 和 counter 更新的时候 计算 counter 一次');
		return counter * 2;
	}, [counter]);
	const counterDoubleOnce = useMemo(() => {
		// console.log('组件挂载计算 counter 一次');
		return counter * 2;
	}, []);
	const counterDoubleJSX = useMemo(() => {
		// console.log('组件挂载 和 counter 更新的时候 计算 counter 一次');
		const doubleValue = counter * 2;
		return (
			<span
				style={{
					paddingRight: doubleValue + "px",
				}}
			>
				{doubleValue}
			</span>
		);
	}, [counter]);

	/*
		useCallback(callback,array) => fn: () => anyValue | jsxVNode
		返回一个函数 作用跟useMemo很像,只不过返回值是一个函数
		当然这个函数也可以返回jsx表达式
	*/
	const [range, setRange] = useState({
		min: 0,
		max: 2,
	});
	const renderList = useCallback(() => {
		//1. 在响应式数据 count 更新时,这里也会更新。
		console.log(" 执行 useCallback render list ");
		let list = [];
		for (let i = 0; i < range.max; i++) {
			list.push(<li key={i}>this li 的 key is : {i}</li>);
		}
		return list;
	}, [range]);
	const renderListMemo = useMemo(() => {
		//1. 在响应式数据 count 更新时,这里也会更新。
		console.log(" 执行 useMemo render list ");
		let list = [];
		for (let i = 0; i < range.max; i++) {
			list.push(<li key={i}>this li 的 key is : {i}</li>);
		}
		return list;
	}, [range]);

	return (
		<div>
			<h1>函数式组件</h1>
			<div>
				<p>counter: {counter}</p>
				<p>counterDouble: {counterDouble}</p>
				<p>counterDoubleOnce: {counterDoubleOnce}</p>
				<p>
					counterDoubleJSX: {counterDoubleJSX} <span>看看表达式的padding</span>
				</p>
				<button onClick={() => setCounter((v) => v + 1)}>click me , counter + 1</button>
				<button onClick={() => setCounter(() => 0)}>click me , counter = 0</button>
			</div>
			<div>
				<p>组件上下文:</p>
				<CtxStore.Provider
					value={{
						counter: counter,
					}}
				>
					<TestJSX__4__1></TestJSX__4__1>
				</CtxStore.Provider>
				<button
					onClick={() =>
						setRange((v) => ({
							...v,
							max: (v.max += 1),
						}))
					}
				>
					setRange
				</button>
				<TestJSX__4__2>{renderList}</TestJSX__4__2>
				<TestJSX__4__3>{renderList}</TestJSX__4__3>

				<TestJSX__4__4>{renderListMemo}</TestJSX__4__4>
				<TestJSX__4__5>{renderListMemo}</TestJSX__4__5>
			</div>
		</div>
	);
}

const TestJSX__4__1 = () => {
	/*
		useContext(store: React.createContext)
		useContext 获取一个 createContext 创建的上下文实例
		返回的是 实例里面的 value
	*/
	const CtxStoreValue = useContext(CtxStore);
	console.log(CtxStoreValue);
	return (
		<>
			<p>函数式组件 子组件 TestJSX__4__1</p>
			<pre>子组件获取上下文 counter : {CtxStoreValue?.counter}</pre>
		</>
	);
};
const TestJSX__4__2 = (props: { children: any }) => {
	/*
		当子组件从父组件得到了一个响应式的数据时,比如 renderList
		此时会重新渲染,可以看看被memo包裹的组件和没有被包裹的组件有什么不同
	*/

	console.log("由于父组件的counter更新,子组件重新渲染 TestJSX__4__2");
	return (
		<>
			<p>函数式组件 子组件 TestJSX__4__2</p>
			<ul>{props.children()}</ul>
		</>
	);
};
const TestJSX__4__3 = memo((props: { children?: any }) => {
	/*
		没错,这里只会执行一次,由于被memo保存了状态,
		同时只有当 renderList 返回的数据不同的时候这里才会重新渲染
		这就是 memo 和 useCallback 的组合
		当然 useMemo 也是可以的,只要返回的是一个jsx表达式
	*/
	console.log("由于父组件的counter更新,子组件重新渲染 TestJSX__4__3");
	return (
		<>
			<p>函数式组件 子组件 TestJSX__4__3</p>
			<ul>{props?.children()}</ul>
		</>
	);
});

const TestJSX__4__4 = (props: { children: any }) => {
	console.log("由于父组件的counter更新,子组件重新渲染 TestJSX__4__4");
	return (
		<>
			<p>函数式组件 子组件 TestJSX__4__4</p>
			<ul>{props.children}</ul>
		</>
	);
};
const TestJSX__4__5 = memo((props: { children?: any }) => {
	/*
		当然 useMemo 也是可以的,只要返回的是一个jsx表达式
	*/
	console.log("由于父组件的counter更新,子组件重新渲染 TestJSX__4__5");
	return (
		<>
			<p>函数式组件 子组件 TestJSX__4__5</p>
			<ul>{props?.children}</ul>
		</>
	);
});

function MapTestJSX() {
	// 函数式组件使用 useHooks 来写

	const [ifShow, setIfShow] = useState(true);
	return (
		<>
			<TestJSX__1></TestJSX__1>
			<TestJSX__2></TestJSX__2>
			<button onClick={() => setIfShow((ifShow) => (ifShow = !ifShow))}>看看组件的生命周期</button>
			{ifShow ? <TestJSX__4></TestJSX__4> : null}
		</>
	);
}

export default MapTestJSX;

.感谢你看完了我写的东西,虽然写了很多注释(生命周期部分的注释还是从别人帖子里直接cv的)

但是我相信没有学过react的人是肯定看不懂的,或许你学了vue,但我还是推荐你敲敲代码看看结果,

官网上的东西很难理解,描述的不清不楚的,还是得看别人的教学视频和帖子才能明白react。

真就不如vue的官网详细,搞不懂为什么广州人这么喜欢用react,

往子组件里传入响应数据还得手动优化useCallback/useMemo + memo避免重复更新,增加开发者的心智负担,服了。

有学react或者觉得我这篇帖子写的还ok的兄弟,可以点赞、评论加收藏。

写到这里都快十二点了,拜拜。

弱鸡程序员年底还在加班
最新回复 (2)
  • 海星吧 2023-3-3
    0 2
    要是觉得ts难写的可以只用js就行了,把注释去掉敲一敲。
    弱鸡程序员年底还在加班
  • 海星吧 2023-3-3
    0 3
    喀秋莎 海星你能做黄油吗?
    弱鸡程序员年底还在加班
    • ACG里世界
      4
          
返回
发新帖