react基础02-非受控组件和受控组件、生命周期
非受控组件和受控组件
非受控组件
class Login extends React.Component { render() { return ( <form onSubmit={this.onSubmit}> 用户名: <input ref={(ele) => (this.username = ele)} type="text" name="username" /> <br /> 密码: <input ref={(ele) => (this.password = ele)} type="password" name="password" /> <br /> <button>登录</button> </form> ) } onSubmit = (e) => { e.preventDefault() const { username, password } = this console.log(`用户名:${username.value},密码:${password.value}`) } } ReactDOM.render(<Login />, document.querySelector('#test'))
受控组件
class Login extends React.Component { state = { username: '', password: '' } render() { return ( <form onSubmit={this.onSubmit}> 用户名: <input onChange={(e) => this.setState({ username: e.target.value })} type="text" name="username" /> <br /> 密码: <input onInput={(e) => this.setState({ password: e.target.value })} type="password" name="password" /> <br /> <button>登录</button> </form> ) } onSubmit = (e) => { e.preventDefault() const { username, password } = this.state console.log(`用户名:${username},密码:${password}`) } } ReactDOM.render(<Login />, document.querySelector('#test'))
如果表单内有多个值,this.setState要写好多遍,将this.setState抽到函数中:(高阶函数和柯里化的写法)
class Login extends React.Component { // #region /* 高阶函数:如果一个函数符合下面2个规范中的任何一个,该函数就是高阶函数 若A函数,接收的参数是一个函数 若A函数,调用的函数返回值是一个函数 常见的高阶函数: new Promise(()=>{})、setTimeout(()=>{},100)、arr.map(()=>{}) 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数形式 */ // #endregion state = { username: '', password: '' } render() { return ( <form onSubmit={this.onSubmit}> 用户名: <input onChange={this.saveFormData('username')} type="text" name="username" /> <br /> 密码: <input onChange={this.saveFormData('password')} type="password" name="password" /> <br /> <button>登录</button> </form> ) } // 函数的柯里化写法 saveFormData = (dataType) => { // 内部函数作为saveFormData的返回值,成为change事件的回调【事件的回调必须是一个函数,要不然还是回调么?】 return (event) => {this.setState({ [dataType]: event.target.value })} } onSubmit = (e) => { e.preventDefault() const { username, password } = this.state console.log(`用户名:${username},密码:${password}`) } } ReactDOM.render(<Login />, document.querySelector('#test'))
不使用柯里化,还可以写成:(都是利用闭包)
class Login extends React.Component { state = { username: '', password: '' } render() { return ( <form onSubmit={this.onSubmit}> 用户名: <input // change事件触发时执行回调函数,回调函数内调用saveFormData方法,将key和value传入 【如果直接写this.saveFormData('username', event) 这是将函数的返回值(undefined)当成是回调函数给change事件用,这能走得通才怪】 onChange={(event) => this.saveFormData('username', event)} type="text" name="username" /> <br /> 密码: <input onChange={(event) => this.saveFormData('password', event)} type="password" name="password" /> <br /> <button>登录</button> </form> ) } saveFormData = (dataType, event) => { this.setState({ [dataType]: event.target.value }) } onSubmit = (e) => { e.preventDefault() const { username, password } = this.state console.log(`用户名:${username},密码:${password}`) } } ReactDOM.render(<Login />, document.querySelector('#test'))
还可以利用bind:
class Login extends React.Component { state = { username: '', password: '' } render() { return ( <form onSubmit={this.onSubmit}> 用户名: <input // bind:当事件被触发时调用saveFormData onChange={this.saveFormData.bind(this, 'username')} type="text" name="username" /> <br /> 密码: <input onChange={this.saveFormData.bind(this, 'password')} type="password" name="password" /> <br /> <button>登录</button> </form> ) } saveFormData = (dataType, event) => { this.setState({ [dataType]: event.target.value }) } onSubmit = (e) => { e.preventDefault() const { username, password } = this.state console.log(`用户名:${username},密码:${password}`) } } ReactDOM.render(<Login />, document.querySelector('#test'))
生命周期(16.x)
/* 初始化(ReactDOM.render()):constructor componentWillMount render componentDidMount 状态更新(this.setState()):shouldComponentUpdate componentWillUpdate render componentDidUpdate 强制更新(this.forceUpdate()):绕过shouldComponentUpdate,执行componentWillUpdate render componentDidUpdate 卸载(ReactDOM.unmountComponentAtNode()):componentWillUnmount 常用: render 必须使用,用来渲染组件 componentDidMount 一般在这里做初始化,如:开启定时器、发送ajax请求、订阅消息 componentWillUnmount 一般在这里做首尾,如:关闭定时器、取消订阅消息 */ class Count extends React.Component { constructor(props) { super(props) console.log('init-constructor') this.state = { count: 0 } } // 挂载前 componentWillMount() { console.log('init-componentWillMount') } // 初始化、更新 render() { console.log('init/update-render') const { count } = this.state return ( <div> <h2>和为:{count}</h2> <button onClick={() => this.setState({ count: count + 1 })}> 点击+1 </button> <button onClick={ReactDOM.unmountComponentAtNode.bind( this, document.querySelector('#test') )} > 卸载 </button> <button onClick={() => this.forceUpdate()}>强制更新</button> </div> ) } // 挂载后 componentDidMount() { console.log('init-componentDidMount') } // 组件是否应该被更新 shouldComponentUpdate() { console.log('update-shouldComponentUpdates') return true } // 更新前 componentWillUpdate() { console.log('update-componentWillUpdate') } // 更新后 componentDidUpdate() { console.log('update-componentDidUpdate') } // 卸载前 componentWillUnmount() { console.log('卸载-componentWillUnmount') } } ReactDOM.render(<Count />, document.querySelector('#test'))
父组件更新props,触发子组件中相应的钩子函数:
/* 父组件更新props(父组件render)触发子组件中钩子:componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate */ class Parent extends React.Component { state = { count: 0 } render() { return ( <div> <h2>父组件</h2> <button onClick={() => { const { count } = this.state this.setState({ count: count + 1 }) }} > 点击+1 </button> <Children count={this.state.count} /> </div> ) } } class Children extends React.Component { constructor(){ super() console.log('init-constructor') } componentWillMount() { console.log('init-componentWillMount') } // 接收新的props时(初始化时传了props,但是不触发此钩子) componentWillReceiveProps(props) { console.log('componentWillReceiveProps', props) } // 是否更新 shouldComponentUpdate() { console.log('shouldComponentUpdate') return true } // 更新前 componentWillUpdate() { console.log('componentWillUpdate') } // 更新后 componentDidUpdate() { console.log('componentDidUpdate') } render() { console.log('init/update-render') return ( <div> <h2>子组件</h2> </div> ) } componentDidMount() { console.log('init-componentDidMount') } } ReactDOM.render(<Parent />, document.querySelector('#test'))
生命周期(17.0.1)
新旧钩子对比,(将要)废弃这3个钩子:(带Will的钩子除了componentWillUnmount其他都要废弃)
componentWillMount
componentWillReceiveProps
componentWillUpdate
如果要用,要加上前缀 UNSAFE_,否则会报警告。实际使用场景极少
新增2个钩子:
getDerivedStateFromProps:state任何时候都取决于props时使用
getSnapshotBeforeUpdate:在旧版render和componentDidUpdate中没有钩子,新版加了这个钩子
这2个钩子的使用场景也极少
class Count extends React.Component { constructor(props) { super(props) console.log('init-constructor') this.state = { count: 0 } } // 初始化、更新 render() { console.log('init/update-render') const { count } = this.state return ( <div> <h2>和为:{count}</h2> <button onClick={() => this.setState({ count: count + 1 })}> 点击+1 </button> <button onClick={ReactDOM.unmountComponentAtNode.bind( this, document.querySelector('#test') )} > 卸载 </button> <button onClick={() => this.forceUpdate()}>强制更新</button> </div> ) } // 挂载后 componentDidMount() { console.log('init-componentDidMount') } // constructor之后,render之前调用。此方法适用于罕见的用例,即state的值在任何时候都取决于props static getDerivedStateFromProps(props, state) { console.log('新-init/update-getDerivedStateFromProps', props, state) return { count1: 100 } // return props 就是将props赋值给state,这将导致组件难以维护 } // 组件是否应该被更新 shouldComponentUpdate() { console.log('update-shouldComponentUpdates') return true } getSnapshotBeforeUpdate(prevProps, prevState) { console.log('新-update-getSnapshotBeforeUpdate', prevProps, prevState) return '快照' } // 更新后 componentDidUpdate(prevProps, prevState, snapshot) { console.log( 'update-componentDidUpdate', prevProps, prevState, snapshot // getSnapshotBeforeUpdate钩子返回的值 ) } // 卸载前 componentWillUnmount() { console.log('卸载-componentWillUnmount') } } ReactDOM.render(<Count />, document.querySelector('#test'))

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> ul { width: 200px; height: 150px; background-color: skyblue; overflow-y: auto; } li { height: 30px; } </style> </head> <body> <div id="test"></div> <script src="./js/17.0.1/react.development.js"></script> <script src="./js/17.0.1/react-dom.development.js"></script> <script src="./js/17.0.1/babel.min.js"></script> <script src="./js/17.0.1/prop-types.js"></script> <script type="text/babel"> class NewList extends React.Component { state = { list: [] } render() { return ( <div> <ul ref="list"> {this.state.list.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> <button onClick={ReactDOM.unmountComponentAtNode.bind( this, document.querySelector('#test') )} > 卸载 </button> </div> ) } componentDidMount() { this.timer = setInterval(() => { const { list } = this.state const data = '新增' + (list.length + 1) this.setState({ list: [data, ...list] }) }, 500) } getSnapshotBeforeUpdate() { return this.refs.list.scrollHeight } componentDidUpdate(prevProps, prevState, height) { this.refs.list.scrollTop += this.refs.list.scrollHeight - height } componentWillUnmount() { clearInterval(this.timer) } } ReactDOM.render(<NewList />, document.querySelector('#test')) </script> </body> </html>
效果:
使用shouldComponentUpdate钩子也可以达到同样的效果:
shouldComponentUpdate() { this.height = this.refs.list.scrollHeight return true } componentDidUpdate() { this.refs.list.scrollTop += this.refs.list.scrollHeight - this.height }
key值的唯一性
使用index作为key的问题:
如果使用index作为key,下一次更新时,index顺序被打乱,会造成key值比对的结果都为false,这就意味着,即使是相同的dom,也需要重新渲染一遍,造成性能上的浪费
/* key值的作用: 当状态中的数据发生变化时,react会根据新数据生成新的虚拟dom,随后react进行【新虚拟dom】和【旧虚拟dom】的diff比较,比较规则如下: ①旧虚拟dom中找到了与新虚拟dom相同的key,若虚拟dom内容没变,直接使用之前的真实dom,若虚拟dom的内容变了,随后替换掉页面中之前的真实dom ②旧虚拟dom中未找到与新虚拟dom相同的key,根据数据创建新的真实dom,随后渲染到页面 用index作为key可能会引发的问题: ①若对数据进行逆序添加、逆序删除等破坏顺序的操作,会造成没有必要的真实dom更新,页面效果没有问题,但效率低 ②如果结构中还包含输入类的dom,会产生错误的dom更新,因为虚拟dom的比对最小颗粒度是标签,输入类的dom会被认为是上一次相同的dom key值的选择: 1、最好用id、手机号、身份证号、学号等唯一值 2、如果不存在对数据的逆序添加和删除等破坏顺序的操作,仅用于渲染列表,使用index作为key没有问题 */
组件传值:
父传子:
传递:给子组件标签上绑定一个自定义属性,值为需要传递的数据
<One username={this.state.name}></One>
接收:在子组件render函数中通过props接收
let { username } = this.props
* react中如何限制传递来的数据类型和设置初始值:
①下载插件:npm i prop-types 从v15.5开始,React.PropTypes助手函数被弃用,使用 prop-types 库来定义 contextTypes
②在子组件中引入:import PropTypes from 'prop-types'
③对当前组件进行类型限制和默认值的设置
// propTypes是组件上的属性,PropTypes是数据类型检测 Two.propTypes = { name: PropTypes.string } Two.defaultProps = { name: '吴小明' }
* vue中是怎样限制类型和设置初始值的:
子传父:
传递:在子组件中通过 this.props.事件函数 来进行传值
<button onClick={this.handleAdd.bind(this)}>点击发送给父组件</button>
handleAdd() { this.props.toApp('我是从two组件传来的') }
接收:在子组件标签上绑定一个自定义属性,值为需要接收参数的函数
<Two toApp={this.handle2.bind(this)}></Two>
handle2(value) {
this.setState({
towValue: value
})
}
非父子:
1、通过onserver传值
①定义observer.js

const eventList = {} const $on = function(eventName, callback) { if (!eventList[eventName]) { eventList[eventName] = [] } eventList[eventName].push(callback) } const $emit = function(eventName, params) { if (eventList[eventName]) { var arr = eventList[eventName] arr.forEach((cb) => { cb(params) }) } } const $off = function(eventName, callback) { if (eventList[eventName]) { if (callback) { var index = eventList[eventName].indexOf(callback) eventList[eventName].splice(index, 1) } else { eventList[eventName].length = 0 } } } export default { $on, $emit, $off }
②在需要传递的组件中引入observer.js
import Observer from '../observer'
通过Observer.$emit()传值:
<button onClick={this.handleClick.bind(this,'传个值给Two组件')}>传个值给Two组件</button>
handleClick(value){ Observer.$emit('abcd',value) }
③在需要接收的组件中引入observer.js
import Observer from '../observer'
通过Observer.$on()接收值
constructor() { super() this.state = { value: '' } Observer.$on('abcd', (value) => { this.setState({ value }) }) }
2、通过context传值
vue中:provide/inject
react中:通过context创建一个生产者,再创建一个消费者供组件使用。其中生产者是父级,消费者是子级。
实践step:
①创建createContext.js文件
import React, { createContext } from 'react' export let { Provider, Consumer } = createContext() // Provider生产者,Consumer消费者
②在父级中引入Provider并将当前根节点包裹,Provider标签中value属性中是一个对象,用于传值
import { Provider } from './createContext' render() { return ( // Provider包裹所有的子级 <Provider value={{ name: '孙艺珍', age: 18 }} > <div className="app"> <One></One> </div> </Provider> ) }
③在自己中引入Consumer并将当前根节点包裹,Consumer中是一个函数返回一个jsx
import { Consumer } from '../createContext' render() { return ( // Consumer包裹的组件接收Provider传来的值,Consumer中是一个函数返回一个jsx语法 <Consumer> {(props) => { let { name, age } = props return ( <div className="Two"> <h1>Two组件</h1> 接收到来自Provider传来的name为:{name},age为:{age} <Three></Three> </div> ) }} </Consumer> ) }
利用高阶组件对Consumer进行二次封装:
①src下创建connect/createContext.js
import React, { createContext } from 'react'
export let { Provider, Consumer } = createContext()
②src下创建高阶组件:hoc/connect.js 用于封装Consumer
import React from 'react' import { Consumer } from '../connect/createContext' export const connect = (WrapperComponent) => { return class extends React.Component { render() { return ( <Consumer> {(props) => { return <WrapperComponent {...props}></WrapperComponent> }} </Consumer> ) } } }
③在父组件中通过Provider传值
import React, { Component } from 'react' import Two from './two' import { Provider } from '../connect/createContext' class One extends Component { render() { return ( <Provider value={{ name: '孙艺珍', sex: '女' }}> <div className="One"> <h1>One组件</h1> <Two></Two> </div> </Provider> ) } } export default One
如果用原来的Consumer接收:
import React, { Component } from 'react' import Three from './three' import { Consumer } from '../connect/createContext' class Two extends Component { render() { return ( <Consumer> {(props) => { let { name, sex } = props return ( <div className="Two"> <h1>Two组件</h1> 姓名:{name},性别:{sex} <Three></Three> </div> ) }} </Consumer> ) } } export default Two
④用封装好的Consumer接收:
import React, { Component } from 'react' import { connect } from '../hoc/connect' class Three extends Component { render() { let { name, sex } = this.props return ( <div className="Three"> <h1>Three组件</h1> 姓名:{name},性别:{sex} </div> ) } } export default connect(Three)
3、redux:公共状态管理
组件分类:
1、类组件 - class App extends React.Component {render (){}}
2、函数组件 - function fn (){return (<div>我是一个函数组件</div>)}
3、ui组件
4、容器组件
5、高阶组件
6、受控组件 - input加上value属性和onChange事件后变为受控组件
7、非受控组件 - input加上defaultValue依旧可以输入内容,此时为非受控组件
react中函数组件和类组件的区别:
函数组件:相比较类组件来说比较轻便、速度较快(16.8中引入hooks来解决函数组件的这个问题)
类组件:有属于自己的生命周期,可以在指定的时间做指定的事,可以存储属于自己的状态
高阶组件:
高阶组件是一个函数,它接收一个组件返回一个相对增强性的组件,简称HOC
react中的插槽:
只需在子组件中通过 this.props.children 进行嵌套的内容。
1、子组件标签中的内容默认是不显示的:
import React from 'react' import Header from './Header' class App extends React.Component { render() { return ( <div className="app"> <Header> <h2>标题</h2> </Header> </div> ) } } export default App
2、子组件中通过 this.props.children 接收
import React from 'react' import './index.css' class Header extends React.Component { render() { return <div className="header">{this.props.children}</div> } } export default Header
生命周期:
1、constructor:
1、当前生命周期是组件在初始化的时候执行的,在constructor中必须要写super()否则this的指向会发生错误
2、可以在当前生命周期中存放当前组件所需的一些状态,这些状态必须要放到this.state中
3、在当前生命周期中访问不到this.props,如果要访问this.props必须要在constructor中传入props
constructor(props) { super(props) this.state = { msg: '孙艺珍' } console.log('constructor', props,this.props) // 在super中传入props才可以用this.props接收到数据 }
2、componentWillMount:
1、当前生命周期是组件挂载前。此时数据和模板还未结合,因此可以在当前生命周期中做数据最后的更改
2、当前生命周期中可以接收到外部的数据,可以访问到this.props
3、在v17.0中被废除,使用时页面会报警告
3、render:(多次执行)
1、当前生命周期是一个渲染函数,是数据和模板相结合的一个函数。当前生命周期在执行的时候会将渲染好的模板在缓存中存储一份,这就是diff算法比较(diff算法:新旧两个虚拟DOM的对比)
2、当前生命周期会多次执行,当this.setState()或者this.props发生改变的时候就会执行
3、可以通过控制shouldComponentUpdate来减少render函数渲染的次数来优化性能
4、componentDidMount:
1、当前生命周期是数据和模板已经结合完毕并且挂载到页面上,因此我们可以在当前生命周期中获取到真实的DOM结构
2、通常会在当前生命周期中进行前后端数据的交互和方法的实例化(swiper)
react中如何访问到DOM节点:
第一种:<h2 ref='h2'></h2>
this.refs.h2
第二种:<h2 ref={(h2)=>{this.h2=h2}}></h2>
this.h2
render() { return ( <div> <h1>One组件</h1> <h2 ref="h2">h2标签</h2> <h3 ref={(h3) => (this.h3 = h3)}>h3标签</h3> </div> ) } componentDidMount() { console.log('挂载后', this.refs.h2,this.h3) // <h2>h2标签</h2> <h3>h3标签</h3> }
5、componentWillReceiveProps:(多次执行)
1、当props的数据发生改变的时候会执行当前生命周期,在当前生命周期函数中有个参数,该参数是新的props
2、当前生命周期在v17.x的版本中被废除
6、shouldComponentUpdate:(多次执行)
1、当前生命周期书写的时候必须return一个布尔值,当值为true时继续执行下面的生命周期;如果为false不执行下面的生命周期
2、当前生命周期中有2个参数,一个是新的props,一个是新的state,可以根据新的props/state与旧的props/state比较减少render函数的渲染次数
3、当前生命周期决定render函数是否渲染,而不是决定数据是否更新
shouldComponentUpdate(newProps, newState) { console.log('决定数据是否更新', newProps, newState) if (this.state.msg === newState.msg) { return false } else { return true } }
7、componentWillUpdate:(多次执行)
1、当前生命周期在数据更新时执行,有2个参数,一个是新的props,一个是新的state
2、可以在当前生命周期中对数据进行最后的更改
注意:
1、尽量不要在当前生命周期中调用this.setState(),死循环
2、当前生命周期在v17.0中被废除
8、componentDidUpdate:(多次执行)
1、当前生命周期在数据更新完后执行,可以在这里获取到数据更新后最新的DOM结构(一定要加边界条件)
2、当前生命周期中有2个参数,一个是旧的props,一个是旧的state
9、componentWillUnmount:
当前生命周期在组件被卸载时执行,可以在当前生命周期做性能的优化:事件的解绑、定时器的移除……
react中如何强制更新数据:
this.forceUpdate()
注:vue中通过this.$forceUpdate()
常见的生命周期面试题:
1、react中哪些生命周期会执行一次,哪些会执行多次
执行一次:
constructor
componentWillMount
componentDidMount
componentWillUnmount
执行多次:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
2、react中第一次执行的生命周期有哪些
constructor
componentWillMount
render
componentDidMount
3、render什么时候被触发
当this.props或this.setState()发生改变的时候触发
4、谈谈对shouldComponentUpdate的理解
5、当this.props或this.setState()执行的时候会触发哪些生命周期
this.props:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
this.setState():
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
css in js:styled-components 将css组件化
安装插件:npm install styled-components
新建styled.js:
import styled, { keyframes } from 'styled-components' import logo from '../../logo.png' // 图片的引入 // 定义动画 const move = keyframes` 0%{ transform:rotate(0deg); } 100%{ transform:rotate(360deg); } ` export const HeaderContainer = styled.div` width: 100%; height: 1rem; background-color: ${(props) => props.color}; /* 可以传参 */ color: #fff; display: flex; justify-content: center; align-items: center; /* 像sass和less一样嵌套 */ button { background-color: yellow; border: 0; animation: ${move} 3s 1s; } button.a { background-color: #c33; } ` // 可以接收父组件传来的值渲染 export const SerachInput = styled.input.attrs((props) => ({ type: props.type, value: props.value }))` width: 60%; height: 0.5rem; border: 0; border-bottom: 1px solid #ccc; background-image: url(${logo}); /* 图片的使用 */ background-repeat: no-repeat; background-size: 100% 100%; ` export const MyButton = styled.button` width: 100px; height: 40px; text-align: center; line-height: 40px; color: black; background-color: yellow; border: 0; animation: ${move} 3s 1s; ` // 继承 export const ChildrenButton = styled(MyButton)` background-color: green; `
index.jsx中引入和使用:
import React, { Component } from 'react' import { HeaderContainer, SerachInput, MyButton, ChildrenButton } from './styled' class Header extends Component { constructor() { super() this.state = { inputVal: '请输入' } } render() { return ( <div> <HeaderContainer color="deeppink"> 猫眼电影 <SerachInput type="text" value={this.state.inputVal} onChange={this.onChange.bind(this)} ></SerachInput> {/* <MyButton>按钮</MyButton> <ChildrenButton>子按钮</ChildrenButton> */} <button>按钮</button> <button className="a">按钮</button> </HeaderContainer> </div> ) } onChange() {} } export default Header
ReactDOM.unmountComponentAtNode
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结