本文是阅读米凯莱·贝尔托利 《React设计模式与最佳实践》 一书的读书笔记,支持做者请点这里购买。javascript
Talk is cheap, just show me the code.css
废话很多说,直接上干货的哈。html
在 React 里,有一种状况是,咱们常常须要根据条件判断决定是否渲染某些组件。就像是这样:前端
<div>
{ isLoggedIn ? <LogoutButton /> : <LoginButton /> }
{ visible && <Modal /> }
</div>
复制代码
当条件判断变得更复杂的请求下,咱们可使用方法和计算属性来取代三目运算和与或判断。java
handleShowLoginButton() {
return this.isLoggedIn && this.isAuthed;
}
get getVisible() {
return this.visible && this.displayMode === "normal"
}
render() {
return (<div> { handleShowLoginButton() ? <LogoutButton /> : <LoginButton /> } { getVisible && <Modal /> } </div>)
}
复制代码
而后黑科技来了,当咱们想要把这些判断逻辑从 render 渲染函数里抽离出来以让渲染函数只负责渲染的时候。咱们就须要用到 render-if
render-only-if
jsx-control-statements
这些辅助依赖了。 客官请看:react
const isShowLoginButton = renderIf(
this.isLoggedIn && this.isAuthed
)
return (<div> { isShowLoginButton(<LoginButton />) } {/* 完告终果 LogoutButton 我还须要另外写一个 isShowLogoutButton 的 renderIf 去判断显示与否吗 */} </div>)
复制代码
而后 render-only-if 本质上是一个高阶函数,它形式上会比 render-if 优雅些。git
const LoginButtonOnlyIf = onlyIf(
({ isLoggedIn && isAuthed }) => {
return isLoggedIn && isAuthed
}
)(LoginButton)
return (
<LoginButtonOnlyIf isLoggedIn={isLoggedIn} isAuthed={isAuthed} /> ) 复制代码
总结:github
而后咱们最后看看 jsx-control-statements 这个恶心东西的用法:npm
<If condition={this.handleShowLoginButton}>
<LoginButton />
</If>
<When condition={this.handleShowLoginButton}>
<LoginButton />
</When>
<When condition={!this.handleShowLoginButton}>
<LogoutButton /> // => 好了这下终于看见咱们的 LogoutButton 出现了
</When>
<Otherwise>
<p>oops.. no condition matched.</p>
</Otherwise>
<ul>
<For each="resultItem" of={this.resultList}>
<li>{resultItem.name}</li>
</For>
// => {resultList.map(resultItem => <li>{resultItem.name}</li>)}
</ul>
复制代码
这是关于性能和可维护性的课题呐。json
始终牢记,设置状态会触发组件从新渲染。所以,应该只将渲染方法要用到的值保存在状态中。
如下是 Dan Abramov (我并不知道他是谁) 建立的帮助咱们作出正确状态选择的步骤:
function shouldIKeepSomethingInReactState() {
if (canICalculateItFromProps()) {
// 不要把 props 属性直接在 state 状态中使用,
// 应该直接在 render() 函数里计算使用它们
return false
}
if (!amIUsingItInRenderMethod()) {
// 不要把没有参与渲染的数据放进 state 状态里,
// 换句话说就是只有须要涉及到组件 render 渲染更新的数据才放到 state 里
return false
}
// 除了以上状况,均可以使用状态。
return true;
}
复制代码
关于 prop 类型检验,React 提供了组件的 propTypes
属性给咱们使用:
const Button = ({text}) => <button>{text}</button>
Button.propTypes = {
text: React.PropTypes.string
}
复制代码
但其实在 TypeScript 的世界里,咱们直接可使用模板类的形式给咱们 React 组件声明 prop 属性接口:
interface IButtonProps = {
text: string;
}
class ButtonClass extend React.Component<IButtonProps, IButtonStates> {}
// => 顺带连 state 属性检验也能够加进来
复制代码
接下来为组件自动生成文档,使用 react-docgen
这个工具。
import React from 'react';
/**
* Sheet 组件
*/
const Sheet = ({title}) => <div>{title}</div>
Sheet.prototype = {
/**
* Sheet 标题
*/
title: React.PropTypes.string
}
复制代码
运行 react-docgen Sheet.js
后结果产出以下 json 描述:
{
"description": "Sheet 组件",
"displayName": "Sheet",
"methods": [],
"props": {
"title": {
"type": {
"name": "string"
},
"required": false,
"description": "Sheet 标题"
}
}
}
复制代码
把这个 json 文件做为团队前端文档项目的输入,就能够自动化地生成可用的组件文档说明啦啦啦。
好了,接下来祭出业内大杀器 storybook。
npm i --save @kadira/react-storybook-addon
复制代码
(貌似 @kadira/react-storybook-addon 已经报废了,建议小伙伴仍是在官网按照文档写本身的 storybook 吧)
故事文档放在 stories
的文件夹中,咱们在 stories 文件夹下建立 sheet.js 定义咱们上面定义组件的故事文档。
// => stories/sheet.js
import React from 'react';
import Sheet from '../src/components/Sheet';
import { storiesOf } from '@kadira/storybook'
storiesOf('Sheet', module)
.add('没有 title 属性的 Sheet 的故事..', () => (
<Sheet/>
))
复制代码
可是咱们要写故事还得在根目录下先来配置好 storybook :
// => .storybook/config.js => 根目录下建立 .storybook 文件夹
import { configure } from '@kadira/storybook';
function loadStories() {
require('../src/stories/sheet')
}
configure(loadStories, module)
复制代码
最后咱们给咱们的 package.json 加上 script 来运行咱们的故事。
"storybook": "start-storybook -p 9001"
复制代码
运行以后在 9001 端口就能够看到故事文档啦啦啦。
关于容器组件和傻瓜组件,我在这里就不说了哈。毕竟是初级内容,很差滥竽充数。咱们直接直奔主题。
好比,最简单的,实现一个给组件加上类名的高阶组件:
const withClassName = Component => props => (
<Component {...props} className="my-class" /> ) 复制代码
上面只是动了一个 prop 而已哈,实际上除了 prop 其余的一切组件属性咱们均可以动哈。
const withTimer = Component => (
class extends React.Component {
constructor(props) {
super(props)
this.state = {
timer: null
}
}
componentDidMount() {
this.timer = setTimeInterval(() => {
console.log('每一个1.5s打印一第二天志哈哈哈')
}, 1500)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
// => 原封不动把接收到的 props 传给 Component
// state 传入 Compnent 实际上是可选项,根据实际需求决定
return <Component {...this.props} {...this.state} /> } } ) // => 而后咱们就能够给普通的组件加上定时打印日志的功能啦 const SheetWithTimer = withTimer(Sheet); 复制代码
而后 recompose 这个库已经帮咱们提供了一些很实用场景的高阶组件,开箱即用哈。
接下来咱们来搞点噱头,看看函数子组件怎么玩。首先,咱们上面那个 withClassName 显然太 low 了,竟然 className 是写死的!?凡事都不要写死,需求之后分分钟给你改。
显然,咱们须要在 withClassName 组件里面再作多一层逻辑,判断好后再动态传 className 给子组件。这个时候咱们为了搞噱头,决定采用函数子组件的模式。
const withClassName = ({children}) => children('my-class'); // => wft,这里 'my-class' 还不照样是写死的...
<withClassName>
{(classname) => <Component className={classname} />} </withClassName>
复制代码
而后,咱们就看到了无限可能... 虽然 withClassName 如今仍是个无状态组件哈,可是咱们彻底能够像 withTimer 组件那样给它加上生命钩子和函数方法还有状态。而后在 render 里不一样的是(咱们不直接使用 <Component /> 而是执行 children()):
render() {
return <Component {...props} /> // => 通常作法 renturn {children(props)} // => 函数子组件作法 } 复制代码
或许更贴切的例子是高阶组件须要作 http 请求的场景吧,把请求回来的数据再传入子组件进行渲染。
<Fetch url="...">
{data => <MyComp data={data} />} </Fetch>
复制代码
首先,简单粗暴的咱们能够在 html 元素里直接写 style,在 jsx 的世界里是长这样的:
<div style={{ fonSize: this.state.fontSize }} />
复制代码
而后 style 行内样式有个缺点,就是你不能直接写媒体查询
和伪类伪元素
,固然动画
(插播小广告:动画 react 库请使用 react-motion)你也无法写。
因此 Radium 应运而生。
有了 Radium ,你能够任性地这样操做:
import radium from 'radium'
const myStyle = {
fontSize: '12px',
':hover': {
color: '#abc'
},
'@media (min-width: 720px)': {
color: '#121212'
}
}
const Div = () => <div style={myStyle} />
export default radium(Div);
复制代码
固然使用媒体查询你还须要在最外层保一个 styleroot 元素,要否则 Radium 哪知道你媒体查询根元素在哪里哟。
import { StyleRoot } from 'radium'
class App extends Component {
render() {
return (
<StyleRoot> <router-view /> </StyleRoot> ) } } 复制代码
固然咱们彻底能够不使用行内样式,而是使用基于 className 的 css 模块。
import styles from './index.less'
render() {
return {
<div className={styles.myDiv} />
}
}
复制代码
/* index.less */
.myDiv {
font-size: 12px
}
/* 默认模块会生成一堆咱们看不懂的英文类名,若是想要让类名不做用在局部而是全局,可使用 :global */
:global .myGlobalDiv {
font-size: 15px
}
/* 咱们还可使用 composes 把其余类的样式混进来 */
.myDivCop {
composes: .myDiv;
color: '#101010'
}
复制代码
再有,若是你的 className 不想写 style.[类名] 的形式而是想直接写字符串类名的形式,你能够借用 react-css-modules 这个库。
而后这种姿式使用:
import cssModules from 'react-css-modules'
import styles from './index.less'
class DivComp extends Component {
render() {
return (
<div className='myDiv' /> ) } } export cssModules(DivComp, styles) 复制代码
而后还有个多是之后趋势的叫 styled-components 的家伙,由于楼主实在是学不动了因此这里就不展开讲了哈。
开玩笑的哈,欢迎评论区留言告诉我你想要阅读的内容主题,我会只选我会的,不会的都不选哈哈哈 我会尽可能抽出时间来撸 demo 和进行延伸阅读的。也欢迎你们关注督促我下期更文。