组件声明
使用ES6 class声明,如果有propTypes则必须写在顶部,lifecycle events必须写在一起
| class |
| propTypes |
| defaultPropTypes |
| constructor |
| event handlers |
| getters |
| render |
| |
| class Person extends React.Component { |
| |
| state propTypes = { |
| firstName: PropTypes.string.isRequired, |
| lastName: PropTypes.string.isRequired, |
| } |
| |
| constructor(props) { |
| super(props) |
| this.state = { smiling : false } |
| } |
| |
| |
| componentWillMount() { |
| |
| } |
| |
| componentDidMount() { |
| |
| } |
| |
| handleClick = () => { |
| |
| } |
| |
| get fullName() { |
| return this.props.firstName + this.props.lastName |
| } |
| |
| render() { |
| return ( |
| <div onClick={this.handleClick}> |
| {this.fullName} { this.state.smiling ? 'is smiling' : '' } |
| </div> |
| ) |
| } |
| } |
| |
计算属性
使用getters封装render所需要的状态或条件的组合
对于返回boolean的getter使用 is- 前缀命名
| |
| render() { |
| return ( |
| <div> |
| { |
| this.state.aghe > 18 |
| && |
| ( |
| this.props.school === 'A' |
| || |
| this.props.school === 'B' |
| ? <VipComponent/> |
| : <NormalComponent/> |
| ) |
| } |
| </div> |
| ) |
| } |
| |
| |
| get isVIP() { |
| return |
| this.state.age > 18 |
| && |
| (this.props.school === 'A' || this.props.school === 'B') |
| } |
| |
| render() { |
| return ( |
| <div> |
| {this.isVIP ? <VipComponent/> : <NormalComponent/> |
| </div> |
| ) |
| } |
事件回调命名
Handler命名风格
- 使用handle开头
- 以事件类型作为结尾(eg: Click, Change)
- 使用一般现在时
| |
| closeAll = () => {} |
| |
| render() { |
| return <div onClick={this.closeAll}/> |
| } |
| |
| |
| handleClick = () => {} |
| render() { |
| return <div onClick={this.handleClick}/> |
| } |
组件化优于多层render
当组件的jsx只写在一个render方法会比较臃肿,更适合拆分成一个组件
视情况采用class component或stateless component
★ render =》 以更大的可能实现组件化开发
| |
| renderItem({name}) { |
| return ( |
| <li> |
| {name} |
| </li> |
| ) |
| } |
| |
| render() { |
| return ( |
| <div className='menu'> |
| <ul> |
| {this.props.items.map(item => this.renderItem(item))} |
| </ul> |
| </div> |
| ) |
| } |
| |
| function Items({name}) { |
| return ( |
| <li> |
| {name} |
| </li> |
| ) |
| } |
| |
| render() { |
| return ( |
| <div className='menu'> |
| <ul> |
| {this.props.items.map(item => <Item {...item}/>)} |
| </ul> |
| </div> |
| ) |
| } |
状态上移优于公共方法
一般组件不应该提供公共方法,会破坏数据流只有一个方向的原则
为了倾向于更细颗粒的组件化,状态应该集中在远离渲染的地方处理(eg: 应用级别的状态就在redux的store中)能够更加方便兄弟组件共享
| |
| class DropDownMun extends Component { |
| constructor(props) { |
| super(props) |
| this.state = { |
| showMenu: false |
| } |
| } |
| |
| show() { |
| this.setState({display: true}); |
| } |
| |
| hide() { |
| this.setState({display: false}); |
| } |
| |
| render() { |
| return this.state.display && ( |
| <div className='dropdown-menu'> |
| |
| </div> |
| ) |
| } |
| } |
| |
| |
| |
| |
| class MyComponent extends Component { |
| showMenu() { |
| this.refs.menu.show(); |
| } |
| hideMenu() { |
| this.refs.menu.hide(); |
| } |
| render() { |
| return <DropDownMenu ref='menu'/> |
| } |
| } |
| |
| |
| |
// 父组件:状态数据控制逻辑
| class MyComponent extends Component { |
| constructor(props) { |
| super(props) |
| |
| |
| this.state = { |
| showMenu: flase |
| } |
| } |
| |
| showMenu() { |
| this.setState({showMenu: true}); |
| } |
| |
| hideMenu() { |
| this.setState({showMenu: false}); |
| } |
| |
| render() { |
| return <DropDownMenu display={this.state.showMenu}/> |
| } |
| } |
| |
| class DropDownMenu extends Component { |
| static propsType = { |
| display: PropTypes.boolean.isRequired |
| } |
| |
| render() { |
| return this.props.display && ( |
| <div className='dropdown-menu'> |
| |
| </div> |
| ) |
| } |
| } |
容器组件
一个容器组件主要负责维护状态和数据的计算,本身没有界面逻辑,只有把结果通过props传递下去。
区分容器组件的目的就是可以把组件的状态和渲染解耦开来,改写界面的时候可以不用关注数据的实现,以得到可复用性。
| |
| class MessageList extends Component { |
| constructor(props) { |
| super(props) |
| this.state = { |
| onlyUnread: false, |
| message: [] |
| } |
| } |
| |
| componentDidMount() { |
| $.ajax({ |
| url: '/api/message', |
| }).then(({message}) => this.setState({message})) |
| } |
| } |
| |
| handleClick = () => this.setState({onlyUnread: !this.state.onlyUnread)} |
| |
| render() { |
| return ( |
| <div class='message'> |
| <ul> |
| { |
| this.state.messages.filter(msg => this.state.onlyUnread ? !msg.asRead : true) |
| .map(({content, author}) => { |
| return <li>{content} - {author}</li> |
| }) |
| } |
| </ul> |
| <button onClick={this.handleClick}>toggle unread</button> |
| </div> |
| ) |
| } |
| |
// good =》 容器组件:创建状态数据+状态数据逻辑处理 ||| 渲染组件:状态数据渲染逻辑
| |
| class MessageContainer extends Component { |
| constructor(props) { |
| super(props) { |
| this.state = { |
| onlyUnread: false, |
| message: [] |
| } |
| } |
| } |
| |
| componentDidMount() { |
| $.ajax({ |
| url: '/api/messages', |
| }).then(({message}) => this.setState({message})); |
| } |
| |
| handleClick = () => this.setState({onlyUnread: !this.state.onlyUnread}); |
| |
| render() { |
| return <MessageList message={this.state.messages.filter(msg => this.state.onlyUnread ? !msg.asRead : true)} |
| toggleUnread={this.handleClick}/> |
| } |
| } |
| |
| |
| function MessageList({messages, toggleUnread}) { |
| return ( |
| <div class='message'> |
| <ul> |
| { |
| messages.map(({content, author}) => { |
| return <li>{content} - {author}</li> |
| } |
| } |
| </ul> |
| <button onClick={toggleUnread}>Toggle Unread</button> |
| |
| </div> |
| ) |
| } |
| |
| MessageList.propTypes = { |
| message: propTypes.array.isRequired, |
| toggleUnread: propTypes.func.isRequired, |
| } |
纯函数的render
render函数应该是一个纯函数(stateless component)
不依赖this.state、this.props以外的变量,也不改变外部状态
| |
| render() { |
| return <div>{window.navigator.userAgent}</div> |
| } |
| |
| |
| render() { |
| return <div>{this.props.userAgent}</div> |
| } |
始终声明PropTypes
// http://facebook.github.io/react/docs/reusable-components.html#prop-validation
Props的非空检测
对于并非isRequired的protype,必须对应设置defaultProps,避免再增加if分支带来的负担
// bad
| render() { |
| if(this.props.person) { |
| return <div>{this.props.person.firstName}</div> |
| } else { |
| return <div>Guest</div> |
| } |
| } |
// Good
| class MyComponent extends Component { |
| render() { |
| return <div>{this.props.person.firstName}</div> |
| } |
| } |
| |
| MyComponent.defaultProps = { |
| person: { |
| firstName: 'Guest', |
| } |
| } |
使用Props初始化
除非props的命名明确指出了意图,否则不应该使用props来初始化state
一般来说不会使用Props对state进行数据初始化,因为该种方式是Anti-Pattern的
| * bad |
| constructor(props) |
| |
| |
| |
| } |
| |
| * good |
| constructor(props) |
| this.state = { |
| items: props.initialItems |
| } |
| } |
| |
classnames
使用classNames来组合条件结果
| * bad |
| render() { |
| return <div className={'menu' + this.props.display ? 'active' : ''}/> |
| } |
| |
| * good |
| render() { |
| const classes = { |
| menu: true, |
| active: this.props.display, |
| } |
| return <div className={classnames(classes)}/> |
| } |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具