react 遍历二维数组是二次发泡吗

从零开始实现React(二):实现组件功能 - CNode技术社区
这家伙很懒,什么个性签名都没有留下。
在上一篇文章中,我们实现了基础的JSX渲染功能,但是React的意义在于组件化。在这篇文章中,我们就要实现React的组件功能。
React定义组件的方式可以分为两种:函数和类,我们姑且将两种不同方式定义的组件称之为函数定义组件和类定义组件
函数定义组件
函数定义组件相对简单,只需要用组件名称声明一个函数,并返回一段JSX即可。
例如我们定义一个Welcome组件:
function Welcome( props ) {
return &h1&Hello, {props.name}&/h1&;
注意组件名称要以大写字母开头
函数组件接受一个props参数,它是给组件传入的数据。
我们可以这样来使用它:
const element = &Welcome name=&Sara& /&;
ReactDOM.render(
document.getElementById( 'root' )
让createElemen支持函数定义组件
回顾一下上一篇文章中我们对React.createElement的实现:
function createElement( tag, attrs, ...children ) {
这种实现只能渲染原生DOM元素,而对于组件,createElement得到的参数略有不同:
如果JSX片段中的某个元素是组件,那么createElement的第一个参数tag将会是一个方法,而不是字符串。
区分组件和原生DOM的工作,是babel-plugin-transform-react-jsx帮我们做的
例如在处理&Welcome name=&Sara& /&时,createElement方法的第一个参数tag,实际上就是我们定义Welcome的方法:
function Welcome( props ) {
return &h1&Hello, {props.name}&/h1&;
所以我们需要修改一下createElement,让它能够渲染组件。
function createElement( tag, attrs, ...children ) {
// 如果tag是一个方法,那么它是一个组件
if ( typeof tag === 'function' ) {
return tag( attrs || {} );
渲染函数定义组件
在简单的修改了createElement方法后,我们就可以用来渲染函数定义组件了。
渲染上文定义的Welcome组件:
const element = &Welcome name=&Sara& /&;
ReactDOM.render(
document.getElementById( 'root' )
在浏览器中可以看到结果:
试试更复杂的例子,将多个组件组合起来:
function App() {
&Welcome name=&Sara& /&
&Welcome name=&Cahal& /&
&Welcome name=&Edite& /&
&/div&
ReactDOM.render(
&App /&,
document.getElementById( 'root' )
在浏览器中可以看到结果:
类定义组件
类定义组件相对麻烦一点,我们通过继承React.Component来定义一个组件:
class Welcome extends React.Component {
render() {
return &h1&Hello, {this.props.name}&/h1&;
为了实现类定义组件,我们需要定义一个Component类:
class Component {}
state & props
通过继承React.Component定义的组件有自己的私有状态state,可以通过this.state获取到。同时也能通过this.props来获取传入的数据。
所以在构造函数中,我们需要初始化state和props
// React.Component
class Component {
constructor( props = {} ) {
this.isReactComponent =
this.state = {};
this.props =
这里多了一个isReactComponent属性,我们后面会用到。
组件内部的state和渲染结果相关,当state改变时通常会触发渲染,为了让React知道我们改变了state,我们只能通过setState方法去修改它。我们可以通过Object.assign来做一个简单的实现。
在每次更新state后,我们需要使用ReactDOM.render重新渲染。
import ReactDOM from '../react-dom'
class Component {
constructor( props = {} ) {
// ...
setState( stateChange ) {
// 将修改合并到state
Object.assign( this.state, stateChange );
if ( this._container ) {
ReactDOM.render( this, this._container );
你可能听说过React的setState是异步的,同时它有很多优化手段,这里我们暂时不去管它,在以后会有一篇文章专门来讲setState方法。
让createElemen支持类定义组件
在js中,class只是语法糖,它的本质仍然是一个函数。
所以第一步,我们需要在createElemen方法中区分当前的节点是函数定义还是类定义。
类定义组件必须有render方法,而通过class定义的类,它的方法都附加在prototype上。
所以只需要判断tag的prototype中是否有render方法,就能知道这个组件是函数定义还是类定义。
现在我们可以进一步修改React.createElement:
function createElement( tag, attrs, ...children ) {
// 类定义组件
if ( tag.prototype &&
tag.prototype.render ) {
return new tag( attrs );
// 函数定义组件
} else if ( typeof tag === 'function' ) {
return tag( attrs || {} );
函数定义组件返回的是jsx,我们不需要做额外处理。但是类定义组件不同,它并不直接返回jsx。而是通过render方法来得到渲染结果。
所以我们需要修改ReactDOM.render方法。
修改之前我们先来回顾一下上一篇文章中我们对ReactDOM.render的实现:
function render( vnode, container ) {
if ( vnode === undefined )
// 当vnode为字符串时,渲染结果是一段文本
if ( typeof vnode === 'string' ) {
const textNode = document.createTextNode( vnode );
return container.appendChild( textNode );
const dom = document.createElement( vnode.tag );
if ( vnode.attrs ) {
Object.keys( vnode.attrs ).forEach( key =& {
if ( key === 'className' ) key = 'class';
// 当属性名为className时,改回class
dom.setAttribute( key, vnode.attrs[ key ] )
vnode.children.forEach( child =& render( child, dom ) );
// 递归渲染子节点
return container.appendChild( dom );
// 将渲染结果挂载到真正的DOM上
在上文定义Component时,我们添加了一个isReactComponent属性,在这里我们需要用它来判断当前渲染的是否是一个组件:
function render( vnode, container ) {
if ( vnode.isReactComponent ) {
const component =
component._container =
// 保存父容器信息,用于更新
vnode = component.render();
//
render()返回的结果才是需要渲染的vnode
// 后面的代码不变...
现在我们的render方法就可以用来渲染组件了。
上面的实现还差一个关键的部分:生命周期。
在React的组件中,我们可以通过定义生命周期方法在某个时间做一些事情,例如定义componentDidMount方法,在组件挂载时会执行它。
但是现在我们的实现非常简单,还没有对比虚拟DOM的变化,很多生命周期的状态没办法区分,所以我们暂时只添加componentWillMount和componentWillUpdate两个方法,它们会在组件挂载之前和更新之前执行。
function render( vnode, container ) {
if ( vnode.isReactComponent ) {
const component =
if ( component._container ) {
if ( component.componentWillUpdate ) {
component.componentWillUpdate();
// 更新
} else if ( component.componentWillMount ) {
component.componentWillMount();
// 挂载
component._container =
// 保存父容器信息,用于更新
vnode = component.render();
// 后面的代码不变...
渲染类定义组件
现在大部分工作已经完成,我们可以用它来渲染类定义组件了。
我们来试一试将刚才函数定义组件改成类定义:
class Welcome extends React.Component {
render() {
return &h1&Hello, {this.props.name}&/h1&;
class App extends React.Component {
render() {
&Welcome name=&Sara& /&
&Welcome name=&Cahal& /&
&Welcome name=&Edite& /&
&/div&
ReactDOM.render(
&App /&,
document.getElementById( 'root' )
运行起来结果和函数定义组件完全一致:
再来尝试一个能体现出类定义组件区别的例子,实现一个计数器Counter,每点击一次就会加1。
并且组件中还增加了两个生命周期函数:
class Counter extends React.Component {
constructor( props ) {
super( props );
this.state = {
componentWillUpdate() {
console.log( 'update' );
componentWillMount() {
console.log( 'mount' );
onClick() {
this.setState( { num: this.state.num + 1 } );
render() {
&div onClick={ () =& this.onClick() }&
&h1&number: {this.state.num}&/h1&
&button&add&/button&
&/div&
ReactDOM.render(
&Counter /&,
document.getElementById( 'root' )
可以看到结果:
mount只在挂载时输出了一次,后面每次更新时会输出update
至此我们已经从API层面实现了React的核心功能。但是我们目前的做法是每次更新都重新渲染整个组件甚至是整个应用,这样的做法在页面复杂时将会暴露出性能上的问题,DOM操作非常昂贵,而为了减少DOM操作,React又做了哪些事?这就是我们下一篇文章的内容了。
这篇文章的代码:
从零开始实现React系列
React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、diff、为什么setState这样设计等问题。
整个系列大概会有六篇左右,我每周会更新一到两篇,我会第一时间在github上更新,有问题需要探讨也请在github上回复我~
关注点star,订阅点watch
上一篇文章
CNode 社区为国内最专业的 Node.js 开源技术社区,致力于 Node.js 的技术研究。
服务器赞助商为
,存储赞助商为
,由提供应用性能服务。
新手搭建 Node.js 服务器,推荐使用无需备案的在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
export class ProjectList extends Component{
constructor(props){
super(props)
this.state={
project:[],
componentDidMount(){
axios.get('http://127.0.0.1:9090/pro/pList').then(res =&{
this.setState({
project:res.data.page.datas
&Select key='project' style={{width:"100%"}}
placeholder="请选择项目"&
this.state.project.map((item,i) =&{
&Option key={`project${i}`} value={item.id}&{item.name}&/Option&
class AddOrderType extends Component{
title="调整角色"
wrapClassName="vertical-center-modal"
visible={this.props.ishow}
onOk={this.handleOk}
onCancel={this.handleCancel}
&Button key="back" size="large" onClick={this.handleCancel}&取消&/Button&,
&Button key="submit" type="primary" size="large" loading={loading} onClick={this.handleOk}&
&/Button&,
{...formItemLayout}
label="所属项目"
{getFieldDecorator('project',{
required: true,
message: '所属项目!'
})(&ProjectList values={this.state.values} onChange={this.onChange} /&)}//这里就是上面声明的组件
&/FormItem&
现在是个弹出框在弹出框里包含了一个这样的select
因为很多地方都要用到这个select
所以我把select又封装了一下。现在弹出框正常 select组件也正常,但是当用户操作了一下select之后关闭弹出框,再次打开,这个select的值就是上次操作的值,并没有被重置掉。如果用value赋值,会导致select变成无法操作的状态.如果用defaultValue的话只能在第一次加载时有效。
不要只说一些理论上的东西,比如通过props重置之类的含糊不清的回答,请在实践后作出合理的回答。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
这算不上封装。将Select组件变为受控组件就可以了。
&Select value={this.props.value} onChange={v =& this.props.onChange(v)}&&/Select&
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
在antd的表单组件里面使用value是无效的,初始化数据使用initialValue,重置数据使用resetFields,具体使用自己看api
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
我之前也和你遇到了一样的困扰,react在遇到双标签组件时候,如果只改变它的child,由于组件自己的props和state没有发生变化,根据diff的算法是不会继续遍历其子组件,于是react就认为这个组件是一样的。
目前我想到的一个解决办法是使用componentWillReceiveProps方法,通过检查传入的选项是否发生了改变,来重置value,达到强制清空的效果。
class mySelect extends React.Component {
constructor(...args) {
super(...args);
this.state = { value: [] };
componentWillReceiveProps(nextprops) {
const { options } = nextprops, oldOpts = this.props.
if ( options.length != oldOpts.length ) {
this.setState( { value: [] } );
for ( let opt of options ) {
if ( !oldOpts.includes(opt) ) {
this.setState( { value: [] } );
render() {
const { options } = this.props, { value } = this.
const option = options.map( val =& &Option key = { val }&{ val }&/Option& );
const select = (
&Select value = { value }&{ option }&/Select&
some anthor tags...
{ select }
其实这个方法并不是一个最好的方法,这不符合react的编程思想,一般这种情况是由于你没有把state统一管理,让组件的state分散在各个层级,所以我建议你最好先理解react的状态提升,让最高层级的组件去管理它的子组件,通过props的形式传递state。
以这个问题为例子,其实你应该在最上层的组件设置好一个value的state,然后以props的形式传入Select组件,那样Select组件便能通过this.props.value知道自己现在选择的选项,当你的选项数据发生了变化的时候,在最上层的组件setState({ value: [] }),这样Select便会清空。
同步到新浪微博
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。版权声明:本文由左明原创文章,转载请注明出处:&文章原文链接:
来源:腾云阁&
这个,叫做竹笕,是中日传统禅文化中常见的庭院装饰品,它的构造可简单可复杂,但原理很简单,比如这个竹笕,水从竹笕顶部入口流入内部,并按照固定的顺序从上向下依次流入各个小竹筒,然后驱动水轮转动。对于强迫症患者来说,观赏竹笕的绝对是一种很享受的过程的最爱,你会发现这些小玩意竟然能这么流畅的协调起来,好神奇。
如果竹笕是一个组件的话,那么水就是组件的数据流。
在React中,数据流是自上而下单向的从父节点传递到子节点,所以组件是简单且容易把握的,他们只需要从父节点提供的props中获取数据并渲染即可。如果顶层组件的某个prop改变了,React会递归地向下遍历整棵组件数,重新渲染所有使用这个属性的组件。
这个是前面看到的 KM 热点问题组件,拥有一个叫做 articles 的属性。
在组件内部,可以通过this.props来访问props,props是组件唯一的数据来源,对于组件来说:
props永远是只读的。
不要尝试在组件内部调用setProps方法来修改props,如果你不小心这么做了,React会报错并给出非常详细的错误提示。
组件的属性类型如果不进行声明和验证,那么很可能使用者传给你的属性值或者类型是无效的,那会导致一些意料之外的故障。好在React已经为我们提供了一套非常简单好用的属性校验机制——
React有一个PropTypes属性校验工具,经过简单的配置即可。当使用者传入的参数不满足校验规则时,React会给出非常详细的警告,定位问题不要太容易。
PropTypes包含的校验类型包括基本类型、数组、对象、实例、枚举——
以及对象类型的深入验证等等。如果内置的验证类型不满足需求,还可以通过自定义规则来验证。如果某个属性是必须的,在类型后面加上 isRequired 就可以了。
React的一大创新,就是把每一个组件都看成是一个状态机,组件内部通过state来维护组件状态的变化,这也是state唯一的作用。
state一般和事件一起使用,我们先看state,然后看看state和事件怎样结合。这是一个简单的开关组件,开关状态会以文字的形式表现在按钮的文本上。首先看render方法,返回了一个button元素,给button注册了一个事件用来处理点击事件,在点击事件中对state的on字段取反,并执行 this.setState() 方法设置on字段的新值。一个开关组件就完成了。
组件渲染完成后,必须有UI事件的支持才能正常工作。
React通过将事件处理器绑定到组件上来处理事件。React事件本质上和原生JS一样,鼠标事件用来处理点击操作,表单事件用于表单元素变化等,Rreact事件的命名、行为和原生JS差不多,不一样的地方是React事件名区分大小写。比如这段代码中,Article组件的section节点注册了一个onClick事件,点击后弹出alert。有时候,事件的处理器需要由组件的使用者来提供,这时可以通过props将事件处理器传进来。
这个是刚才那个Article组件的使用者,它提供给Article组件的props中包含了一个onClick属性,这个onClick指向这个组件自身的一个事件处理器,这样就实现了在组件外部处理事件回调。
这是一个React组件实现组件可交互所需的流程,render()输出虚拟DOM,虚拟DOM转为DOM,再在DOM上注册事件,事件触发setState()修改数据,在每次调用setState方法时,React会自动执行render方法来更新虚拟DOM,如果组件已经被渲染,那么还会更新到DOM中去。
这些是React目前支持的事件列表。
React的组件拥有一套清晰完整而且非常容易理解的生命周期机制,大体可以分为三个过程:初始化、更新和销毁,在组件生命周期中,随着组件的props或者state发生改变,它的虚拟DOM和DOM表现也将有相应的变化。
首先是初始化过程,这里会着重讲,需要充分理解。组件类在声明时,会先调用 getDefaultProps() 方法来获取默认props值,这个方法会且只会在声明组件类时调用一次,这一点需要注意,它返回的默认props由所有实例共享。在组件被实例化之前,会先调用一次实例方法 getInitialState() 方法,用于获取这个组件的初始state。实例化之后就是渲染,componentWillMount方法会在生成虚拟DOM之前被调用,你可以在这里对组件的渲染做一些准备工作,比如计算目标容器尺寸然后修改组件自身的尺寸以适应目标容器等等。接下来就是渲染工作,在这里你会创建一个虚拟DOM用来表示组件的结构。对于一个组件来说,render 是唯一一个必须的方法。render方法需要满足这几点:1.只能通过 this.props 或 this.state 访问数据2.只能出现一个顶级组件3.可以返回 null、false 或任何 React 组件4.不能对 props、state 或 DOM 进行修改需要注意的是,render 方法返回的是虚拟DOM。
渲染完成以后,我们可能需要对DOM做一些操作,比如截屏、上报日志、或者初始化iScroll等第三方非React插件,可以在 componentDidMount() 方法中做这些事情。当然,你也可以在这个方法里通过 this.getDOMNode() 方法取得最终生成DOM节点,然后对DOM节点做爱做的事情,但需要注意做好安全措施,不要缓存已经生成的DOM节点,因为这些DOM节点随时可能被替换掉,所以应该在每次用的时候去读取。
组件被初始化完成后,它的状态会随着用户的操作、时间的推移、数据更新而产生变化,变化的过程是组件声明周期的另一部分 ——
更新过程。
当组件已经被实例化后,使用者调用 setProps() 方法修改组件的数据时,组件的 componentWillReceiveProps() 方法会被调用,在这里,你可以对外部传入的数据进行一些预处理,比如从props中读取数据写入state。
默认情况下,使用者调用组件的 setProps() 方法后,React会遍历这个组件的所有子组件,进行“灌水”,将props从上到下一层一层传下去,并逐个执行更新操作,虽然React内部已经进行过很多的优化,这个过程并不会花费多少时间,但是程序员里永远不缺乏长期性能饥渴的同学,不用担心,React有一个能够解决你性能饥渴的办法——shouldComponentUpdate()。有时候,props发生了变化,但组件和子组件并不会因为这个props的变化而发生变化,打个比方,你有一个表单组件,你想要修改表单的name,同时你能够确信这个name不会对组件的渲染产生任何影响,那么你可以直接在这个方法里return false来终止后续行为。这样就能够避免无效的虚拟DOM对比了,对性能会有明显提升。如果这个时候有同学仍然饥渴难耐,那么你可以尝试 不可变数据结构(用过mongodb的同学应该懂)。组件在更新前,React会执行componentWillUpdate() 方法,这个方法类似于前面看到的 componentWillMount()方法,唯一不同的地方只是这个方法在执行的时候组件是已经渲染过的。需要注意的是,不可以在这个方法中修改props或state,如果要修改,应当在 componentWillReceiveProps() 中修改。然后是渲染,React会拿这次返回的虚拟DOM和缓存中的虚拟DOM进行对比,找出【最小修改点】,然后替换。更新完成后,React会调用组件的componentDidUpdate 方法,这个方法类似于前面 componentDidMount 方法,你仍然可以在这里可以通过 this.getDOMNode() 方法取得最终的DOM节点。
香港电影结尾经常看到一个剧情,就是英雄打败了坏人,然后警察出来擦屁股——
警察偶尔还能立功,而 componentWillUnmount 最可怜,他除了擦屁股什么也做不了。你可以在这个方法中销毁非React组件注册的事件、插入的节点,或者一些定时器之类。这个过程可能容易出错,比如绑定了事件却没销毁,这个React可帮不了你,你自己约的炮,含着泪也要打完。
两节内容讲了上手React所必备的知识。后面讲价值。
直出有多快我就不多说了。因为有虚拟DOM的存在,React可以很容易的将虚拟DOM转换为字符串,这便使我们可以只写一份UI代码,同时运行在node里和和浏览器里。
在node里将组件HTML渲染为一段HTML一句话即可。不过围绕这个renderToString我们还要做一些准备工作。代码有点多,大家做好心理准备。
这是一个express的路由方法,在这里:1.从后台server或数据库等来源拉取数据2.引入要渲染的React组件3.调用React.renderToString()方法来生成HTML4.最后发送HTML和数据给浏览器
这里为了方便描述,没有写拉取数据的代码,大家自行脑补。
需要注意的是这里的JSON字符串中可能出现结尾标签或HTML注释,可能会导致语法错误,这里需要进行转义。
页面的示例代码本来打算用大家更熟悉的HTML,但发现代码量太多了PPT里一页放不下,所以换成了jade代码,没用过jade的同学也顺便了解一下,我也顺便给jade打个广告。这个页面做了X个事:1.将前面在action里生成的HTML写到#container元素里2.引入必须的JS文件3.获取action提供的数据4.渲染组件
这就是React的服务端渲染,组件的代码前后端都可以复用。&有没有没理解清楚的同学?&
是不是感觉React挺牛逼的?大家以为React就这么点能耐吗?
React能够用一套代码同时运行在浏览器和node里,而且能够以原生App的姿势运行在iOS和Android系统中,即拥有了web迭代迅速的特性,又拥有原生App的体验。这个姿势叫做 React-Native。这是React和React-Native在github上的数据,可以看出React-Native也是相当热门——因为React-Native能够使React的价值最大化,这个价值是什么呢——对业务来说,意味着不需要为了做终端版本就招聘和前端等量人力的终端开发,同时意味着我们成为全栈工程师有了一个捷径。
了解iOS开发的同学都知道,水果公司对应用上架的审核效率实在让人无力吐槽,很多团队上一个版本还没审核结束,下一个版本就已经做好了。而React-Native支持从网络拉取JS,这样iOS应用也能够像web一样实现快速迭代了。
这个是react-native的调试过程
作为一个没写过一句Object-C代码的web前端开发,我只用了一天时间就上手了react-native,然后用了半天时间做出了一个简单的demo页面,可以看到react-native的生产效率还是非常高的。
单元测试顾名思义,是对各个模块进行最小范围的测试,容易。我们来演示一个checkbox的单元测试过程。
因为虚拟DOM的存在,使得react的代码很容易做好单元测试,这是上面那段代码的测试用例,通过karma执行后即可看到结果。
所以你可能需要这些东西
(如果你已经看到这里了,为何不再花1分钟思考一下上面3个问题)
上一期React技术文章:
阅读(...) 评论()404 Not Found
The requested URL /q/3714 was not found on this server.

我要回帖

更多关于 react 二进制转base64 的文章

 

随机推荐