React组件设计技巧
React组件设计组件分类展示组件和容器组件
下面是一个可能会经常写的组件,评论列表组件,数据交互和展示都放到了一个组件里面。 // CommentList.js class CommentList extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json",dataType: 'json',success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return <ul> {this.state.comments.map(renderComment)} </ul>; } renderComment({body,author}) { return <li>{body}—{author}</li>; } } 我们对上面的组件进行拆分,把他拆分成容器组件 // CommentListContainer.js class CommentListContainer extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json",success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return <CommentList comments={this.state.comments} />; } } // CommentList.js class CommentList extends React.Component { constructor(props) { super(props); } render() { return <ul> {this.props.comments.map(renderComment)} </ul>; } renderComment({body,author}) { return <li>{body}—{author}</li>; } } 优势:
有状态组件和无状态组件下面是一个最简单的无状态组件的例子: function HelloComponent(props,/* context */) { return <div>Hello {props.name}</div> } ReactDOM.render(<HelloComponent name="Sebastian" />,mountNode) 可以看到,原本需要写“类”定义( 结合 function Input({ label,name,value,...props },{ defaultTheme }) { const { theme,autoFocus,...rootProps } = props return ( <label htmlFor={name} children={label || defaultLabel} {...rootProps} > <input name={name} type="text" value={value || ''} theme={theme || defaultTheme} {...props} /> )} Input.contextTypes = {defaultTheme: React.PropTypes.object}; 无状态组件不像上述两种方法在调用时会创建新实例,它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部优化。 无状态组件不支持 "ref" 高阶组件高阶组件通过函数和闭包,改变已有组件的行为,本质上就是 当写着写着无状态组件的时候,有一天忽然发现需要状态处理了,那么无需彻底返工:) 高阶组件加无状态组件,则大大增强了整个代码的可测试性和可维护性。同时不断“诱使”我们写出组合性更好的代码。 高阶函数function welcome() { let username = localStorage.getItem('username'); console.log('welcome ' + username); } function goodbey() { let username = localStorage.getItem('username'); console.log('goodbey ' + username); } welcome(); goodbey(); 我们发现两个函数有一句代码是一样的,这叫冗余唉。(平时可能会有一大段代码的冗余)。 下面我们要写一个中间函数,读取username,他来负责把username传递给两个函数。 function welcome(username) { console.log('welcome ' + username); } function goodbey(username) { console.log('goodbey ' + username); } function wrapWithUsername(wrappedFunc) { let newFunc = () => { let username = localStorage.getItem('username'); wrappedFunc(username); }; return newFunc; } welcome = wrapWithUsername(welcome); goodbey = wrapWithUsername(goodbey); welcome(); goodbey(); 好了,我们里面的 举一反三的高阶组件下面是两个冗余的组件。 import React,{Component} from 'react' class Welcome extends Component { constructor(props) { super(props); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return ( <div>welcome {this.state.username}</div> ) } } export default Welcome; import React,{Component} from 'react' class Goodbye extends Component { constructor(props) { super(props); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return ( <div>goodbye {this.state.username}</div> ) } } export default Goodbye; 我们可以通过刚刚高阶函数的思想来创建一个中间组件,也就是我们说的高阶组件。 import React,{Component} from 'react' export default (WrappedComponent) => { class NewComponent extends Component { constructor() { super(); this.state = { username: '' } } componentWillMount() { let username = localStorage.getItem('username'); this.setState({ username: username }) } render() { return <WrappedComponent username={this.state.username}/> } } return NewComponent } import React,{Component} from 'react'; import wrapWithUsername from 'wrapWithUsername'; class Welcome extends Component { render() { return ( <div>welcome {this.props.username}</div> ) } } Welcome = wrapWithUsername(Welcome); export default Welcome; import React,{Component} from 'react'; import wrapWithUsername from 'wrapWithUsername'; class Goodbye extends Component { render() { return ( <div>goodbye {this.props.username}</div> ) } } Goodbye = wrapWithUsername(Goodbye); export default Goodbye; 看到没有,高阶组件就是把 为了代码的复用性,我们应该尽量减少代码的冗余。
组件开发基本思想单功能原则使用react时,组件或容器的代码在根本上必须只负责一块UI功能。 让组件保持简单
基本准则
组件开发技巧form表单里的受控组件和不受控组件受控组件在大多数情况下,我们推荐使用受控组件来实现表单。在受控组件中,表单数据由 React 组件负责处理。下面是一个典型的受控组建。 class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } } 设置表单元素的 对于受控组件来说,每一次 不受控组件因为不受控组件的数据来源是 DOM 元素,当使用不受控组件时很容易实现 React 代码与非 React 代码的集成。如果你希望的是快速开发、不要求代码质量,不受控组件可以一定程度上减少代码量。否则。你应该使用受控组件。 一般情况下不受控组件我们使用 class NameForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(event) { alert('A name was submitted: ' + this.input.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={(input) => this.input = input} /> </label> <input type="submit" value="Submit" /> </form> ); } } 组件条件判断三元函数组件判断渲染const sampleComponent = () => { return isTrue ? <p>True!</p> : <p>false!</p> }; 使用&&表达式替换不必要的三元函数const sampleComponent = () => { return isTrue ? <p>True!</p> : <none/> }; const sampleComponent = () => { return isTrue && <p>True!</p> }; 需要注意的是如果 多重嵌套判断// 问题代码 const sampleComponent = () => { return ( <div> {flag && flag2 && !flag3 ? flag4 ? <p>Blah</p> : flag5 ? <p>Meh</p> : <p>Herp</p> : <p>Derp</p> } </div> ) }; 解决方案:
const sampleComponent = () => { const basicCondition = flag && flag2 && !flag3; if (!basicCondition) return <p>Derp</p>; if (flag4) return <p>Blah</p>; if (flag5) return <p>Meh</p>; return <p>Herp</p> } setState异步性在某些情况下, 但是,有一些行为也会阻止
解决setState函数异步的办法?根据 this.setState({count: 1},() => { console.log(this.state.count); // 1 }) React源码中setState的实现ReactComponent.prototype.setState = function(partialState,callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null,'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.' ); this.updater.enqueueSetState(this,partialState); if (callback) { this.updater.enqueueCallback(this,callback,'setState'); } };
依赖注入在 高阶组件// inject.jsx var title = 'React Dependency Injection'; export default function inject(Component) { return class Injector extends React.Component { render() { return ( <Component {...this.state} {...this.props} title={ title } /> ) } }; } // Title.jsx export default function Title(props) { return <h1>{ props.title }</h1>; } // Header.jsx import inject from './inject.jsx'; import Title from './Title.jsx'; var EnhancedTitle = inject(Title); export default function Header() { return ( <header> <EnhancedTitle /> </header> ); } context
var context = { title: 'React in patterns' }; class App extends React.Component { getChildContext() { return context; } // ... } App.childContextTypes = { title: PropTypes.string }; class Inject extends React.Component { render() { var title = this.context.title; // ... } } Inject.contextTypes = { title: PropTypes.string }; 之前的 这里的分形架构指的是从理想的 但如果根组件树中有任意一个组件使用了支持透传的 并且他有一个致命缺陷:任何一个中间传递的组件 新的Context Api 新的 新的 Context API 分为三个组成部分:
const ThemeContext = React.createContext('light'); class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton(props) { return ( <ThemeContext.Consumer> {theme => <Button {...props} theme={theme} />} </ThemeContext.Consumer> ); } 事件处理中的this指向问题class Switcher extends React.Component { constructor(props) { super(props); this.state = { name: 'React in patterns' }; } render() { return ( <button onClick={ this._handleButtonClick }> click me </button> ); } _handleButtonClick() { console.log(`Button is clicked inside ${ this.state.name }`); // 将导致 // Uncaught TypeError: Cannot read property 'state' of null } } 我们可以通过下面三种方式简单实现this指向的绑定:
给setState传入回调函数setState() 不仅能接受一个对象,还能接受一个函数作为参数呢,该函数接受该组件前一刻的 state 以及当前的 props 作为参数,计算和返回下一刻的 state。 // assuming this.state.count === 0 this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); this.setState({count: this.state.count + 1}); // this.state.count === 1,not 3 this.setState((prevState,props) => ({ count: prevState.count + props.increment })); // Passing object this.setState({ expanded: !this.state.expanded }); // Passing function this.setState(prevState => ({ expanded: !prevState.expanded })); 组件切换技巧import HomePage from './HomePage.jsx'; import AboutPage from './AboutPage.jsx'; import UserPage from './UserPage.jsx'; import FourOhFourPage from './FourOhFourPage.jsx'; const PAGES = { home: HomePage,about: AboutPage,user: UserPage }; const Page = (props) => { const Handler = PAGES[props.page] || FourOhFourPage; return <Handler {...props} /> }; React style组件分类基础组件, 布局组件, 排版组件 给无状态的纯UI组件应用样式请保持样式远离那些离不开state的组件. 比如路由,视图,容器,表单,布局等等不应该有任何的样式或者css class出现在组件上. 相反,这些复杂的业务组件应该有一些带有基本功能的无状态UI组件组成. class SampleComponent extends Component { render() { return ( <form onSubmit={this.handleSubmit}> <Heading children='Sign In'/> <Input name='username' value={username} onChange={this.handleChange}/> <Input type='password' name='password' value={password} onChange={this.handleChange}/> <Button type='submit' children='Sign In'/> </form> ) } } // 表达组件(带样式) const Button = ({ ...props }) => { const sx = { fontFamily: 'inherit',fontSize: 'inherit',fontWeight: 'bold',textDecoration: 'none',display: 'inline-block',margin: 0,paddingTop: 8,paddingBottom: 8,paddingLeft: 16,paddingRight: 16,border: 0,color: 'white',backgroundColor: 'blue',WebkitAppearance: 'none',MozAppearance: 'none' } return ( <button {...props} style={sx}/> ) } 样式模块(style module)一般来说,在组件内写死(hard code)样式应该是要被避免的. 这些有可能被不同的UI组件分享的样式应该被分开放入对应的模块中. // 样式模块 export const white = '#fff'; export const black = '#111'; export const blue = '#07c'; export const colors = { white,black,blue }; export const space = [ 0,8,16,32,64 ]; const styles = { bold: 600,space,colors }; export default styles // button.jsx import React from 'react' import { bold,colors } from './styles' const Button = ({ ...props }) => { const sx = { fontFamily: 'inherit',fontWeight: bold,paddingTop: space[1],paddingBottom: space[1],paddingLeft: space[2],paddingRight: space[2],color: colors.white,backgroundColor: colors.blue,MozAppearance: 'none' }; return ( <button {...props} style={sx}/> ) }; 样式函数(Style Functions)// Modular powers of two scale const scale = [ 0,64 ]; // 通过这个函数去取得一部分的样式 const createScaledPropertyGetter = (scale) => (prop) => (x) => { return (typeof x === 'number' && typeof scale[x] === 'number') ? {[prop]: scale[x]} : null }; const getScaledProperty = createScaledPropertyGetter(scale); export const getMargin = getScaledProperty('margin'); export const getPadding = getScaledProperty('padding'); // 样式函数的用法 const Box = ({ m,p,...props }) => { const sx = { ...getMargin(m),...getPadding(p) }; return <div {...props} style={sx}/> }; // 组件用法. const Box = () => ( <div> <Box m={2} p={3}> A box with 16px margin and 32px padding </Box> </div> ); 常见小坑state不更新?class SampleComponent extends Component { // constructor function (or getInitialState) constructor(props) { super(props); this.state = { flag: false,inputVal: props.inputValue }; } render() { return <div>{this.state.inputVal && <AnotherComponent/>}</div> } } 这样做的危险在于,有可能组件的 class SampleComponent extends Component { // constructor function (or getInitialState) constructor(props) { super(props); this.state = { flag: false }; } render() { return <div>{this.props.inputValue && <AnotherComponent/>}</div> } } 更干净的render函数?更干净的 其实在这里干净是指我们在 class Table extends PureComponent { render() { return ( <div> {this.props.items.map(i => <Cell data={i} options={this.props.options || []}/> )} </div> ); } } 这种写法的问题在于 仔细观察你会发现, const defaultval = []; // <--- 也可以使用defaultProps class Table extends PureComponent { render() { return ( <div> {this.props.items.map(i => <Cell data={i} options={this.props.options || defaultval}/> )} </div> ); } } 还是多次重新渲染class App extends PureComponent { render() { return <MyInput onChange={e => this.props.update(e.target.value)}/>; } } class App extends PureComponent { update(e) { this.props.update(e.target.value); } render() { return <MyInput onChange={this.update.bind(this)}/>; } } 在上面的两个坏实践中,每次我们都会去创建一个新的函数实体. 和第一个例子类似,新的函数实体会让我们的浅比较返回 class App extends PureComponent { constructor(props) { super(props); this.update = this.update.bind(this); } update(e) { this.props.update(e.target.value); } render() { return <MyInput onChange={this.update}/>; } } 命名引用命名React模块名使用帕斯卡命名,实例使用骆驼式命名 // bad import reservationCard from './ReservationCard'; // good import ReservationCard from './ReservationCard'; // bad const ReservationItem = <ReservationCard />; // good const reservationItem = <ReservationCard />; 高阶模块命名// bad export default function withFoo(WrappedComponent) { return function WithFoo(props) { return <WrappedComponent {...props} foo />; } } // good export default function withFoo(WrappedComponent) { function WithFoo(props) { return <WrappedComponent {...props} foo />; } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFoo.displayName = `withFoo(${wrappedComponentName})`; return WithFoo; } 属性命名避免使用DOM相关的属性来用作其他的用途。 // bad <MyComponent style="fancy" /> // good <MyComponent variant="fancy" /> 私有函数添加 _ 前缀?在React模块中,不要给所谓的私有函数添加 _ 前缀,本质上它并不是私有的。 为什么? Ordering React 模块生命周期class extends React.Component 的生命周期函数:
点击回调或者事件处理器 如
可选的
如何定义 import React from 'react'; import PropTypes from 'prop-types'; const propTypes = { id: PropTypes.number.isRequired,url: PropTypes.string.isRequired,text: PropTypes.string,}; const defaultProps = { text: 'Hello World',}; class Link extends React.Component { static methodsAreOk() { return true; } render() { return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>; } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link; (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |