组件生命周期部分:参考链接: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的兄弟,可以点赞、评论加收藏。
写到这里都快十二点了,拜拜。