React 组件渲染的怪事

开门见山

首先入口组件App
没什么东西就是一些布局(antd的组件),然后我自己加了3个Link用于点击测试,一个自定义的DDD组件
function App({children}) {
  return (
    <Layout>
      <Header>Header</Header>
      <Layout>
        <Sider style={{height:500}}>Sider</Sider>
        <Content style={{height:500}}>
          <h3>
            <Link to="/"> 首页</Link>
            <Link to="/aaa"> AAA</Link>
            <Link to="/bbb"> BBB</Link>
          </h3>
          <DDD>
            {children}
          </DDD>
        </Content>
      </Layout>
      <Footer>Footer</Footer>
    </Layout>
  )
}

export default withRouter(connect()(IndexPage))
DDD组件:
function DDD(props) {
 return (
    <div>
      {props.children}
    </div>
  );
}
export default connect()(DDD);
router.js
return (
  <HashRouter>
      <App>
        <Switch>
          <Route path="/aaa" component={AAA} />
          <Route path="/bbb" component={BBB} />
        </Switch>
      </App>
  </HashRouter>
);

主要上面这3个文件,上面这个情况下发现一个问题:
》》》点击App组件里Link,并不会展示对应的路由页面
刚开始觉得嘛是自定义的DDD组件用了connect,导致路由更新被阻碍了,确实,我给DDD组件的connect外面套了个withRouter,可以了

但是关键不在这!
我发现我不给DDD组件加withRouter,而是在App组件的{children}随便加点什么,再去点击Link就可以渲染对应的路由组件了
即:

function App({children}) {
  return (
    <Layout>
      <Header>Header</Header>
      <Layout>
        <Sider style={{height:500}}>Sider</Sider>
        <Content style={{height:500}}>
          <h3>
            <Link to="/"> 首页</Link>
            <Link to="/aaa"> AAA</Link>
            <Link to="/bbb"> BBB</Link>
          </h3>
          <DDD>
            1111111
            {children}
          </DDD>
        </Content>
      </Layout>
      <Footer>Footer</Footer>
    </Layout>
  )
}

export default withRouter(connect()(IndexPage))

??????????????
完全无法理解了,google了很多,文档也看了,一天下来还是无法想通,好难受,憋得慌,求大佬给个真相!

主要搞后端的,闲着就自学了Vue,从上个星期开始学React的,希望大佬可以稍微说的详细些,谢谢大家^_^

阅读 2.8k
4 个回答

大兄弟, 你很幸运, 我每天就上来回答一两道题, 今天刚好挑到了你的.

<DDD>
   {children}
</DDD>

// 等价于

<DDD children={children} />

这种写法不跟新的原因是因为, DDD 组件的 children 属性一直都等于 children 对象. connect 后的 DDD 组件 shouldComponentUpdate 会因为 props 没有变化而返回 false, 阻止了组件更新.

<DDD>
   111
   {children}
</DDD>

// 等价于

<DDD children={[111, children]} />

这种写法呢, 你传递给 DDD 的 children 属性就是一个数组了 [111, children]. 这种情况下 DDD 的 children 属性永远不会相同, 因为每次都会生成一个新数组, 导致每次 DDD 每次接收到的 props 都相同, 所以可以正常更新.

原因就是react-router@4.x完全变成Component了。
这样一来,connect高阶函数无法直接与router关联,需要withRouterconnect高阶函数返回的组件与router关联起来,这样,router才能认识。
大概就这样吧。最好还是看react-router的文档吧。

@liximomo 的回答很详细了,基本就是这样了。

另外React是不去判断是否需要更新的, 而比较新的react引入了pureComponent的概念,其实就是用shadowEqual重写了ShouldComponentUpdate方法的普通组件而已。 里面就一行代码

然而connect方法重写的ShouldComponentUpdate方法, 代码:https://github.com/reduxjs/re...

你可以在这里debug看一下,应该是selector.shouldComponentUpdate = false。
导致后续不更新。

withRouter是react-router之后引入的API,我好久不做react开发了,不记得了,就不多评论

今天弄指纹,遇到点麻烦,挺晚了才看到各位的回复,先谢谢大家的热心回复,Thx?

1.@小翼、@说好的一血呢 两位说的东西,我都是了解的,不敢说深入骨髓,但是对于你们说的东西所该知道的影响和作用还是清楚的

2.十分感谢@liximomo的回答,谢谢大佬的点拨,让我把问题确定在了DDD的children上
children是DDD的一个prop,这个prop是否发生改变会成为DDD组件是否会去重新渲染的关键,也就是说问题变成了DDD是否更新跟DDD组件接收到的children是否发生变化有关

虽然大佬跟我细说了原因,但是我还是很好奇,凭什么传个对象给children就比较出来是没变,传个数组就比较出来是变了

但是当我知道问题的核心所在,我开始转换思路寻找对应方向的文档和资料

首先找到的一些还是和大佬差不多的描述,还是无法说服自己

Instead of doing stuff like <MyComponent items={['one', 'two']} />, which will lead to new props each time and make shouldComponentUpdate evaluate to true (thus triggering a render()), you're instructed to do something like:
const items = [1, 3, 4]

<MyComponent items={items} /> This is so react doesn't think some mutation occurred. We point to the same reference (place in memory) each time, soshouldComponentUpdate will evaluate to false and no pointless paints will occur

最后我又去看文档,找到了一些很有用信息,但是初看都忽略了,就是文档末的一些引用(还是没有全部都点过去看完,英文不是很好,看起来也是有点吃力的)

其中有这样一篇引用的文章:http://reactkungfu.com/2015/0...

主要是说了javacript世界里对象的比较:
有2种比较:
一种是引用比较----地址相同即相同
一种是值相同----值相同即相同
不严谨地说:引用比较,顾名思义,地址相同的对象,我当他们是相等的,这个前提下,x=12,y=12,由于创建变量x的地址和创建变量y的地址不同,所以x和y不相等,想想就知道这样导致多大的麻烦,所以有了一个值比较,即当x,y这样的number类型就被当做像java里的基本类型一样,他们是用值比较进行的。
另外值得一提的是,数组的push,pop…等方法也是相当于用了…运算符,其实数组对象的地址已经是变了的

而react为了性能采用的是浅层比较,如果需要深层比较需要自己实现shouldComponentUpdate方法。此时,浅层比较意味着什么?就是前面说的引用比较,就是你给我的东西引用地址变了那就是变了,没变就是没变,也是因此redux中我们需要用…扩展运算符返回一个新的state。

回到我的问题,这个时候就变得明朗了,原来传进DDD组件只包裹了一个子节点,直接把这个子节点对象传给了DDD,而这个对象一直没变(@liximomo说了没变,但是我不知道为什么这个children一直不变)。

而当DDD组件包裹的是多个子节点的时候,会新建一个数组对象(为什么新建一个数组对象?DDD组件需要获取children,子节点又是多个,要拿个东西放放)然后把所有子节点push进去,每一次的地址都是变化的(这个是肯定的),也因此真正地导致了DDD组件的更新。

为什么对象就没变?
a={x:'1'};//a:App的children
b=a;//b:DDD的children
a.x='7';这个时候即使App的children的内容发生变化,结果如下一样,js还是认为没有发生过变化
console.log(a===b);//true

再写一个:

a=[1,2,3];
b=a;
a[0]=7;
console.log(a===b);//true

当DDD.props.children=App.props.children这个关系存在之后,无论App.props.children怎么变都没用

为什么数组就变了
a={x:'1'};//a:App的children
b=[a,'我加的额外的东东'];//新建一个数组并且赋给了b,场景下就是:把App的children和我额外放在DDD组件下的东西组成了一个数组
console.log(a===b);//false
b=[a,'我加的额外的东东'];//哪怕数组里面的东西一层不变,但是因为数组是新的,所以浅层比较还是不相同
console.log(a===b);//false

到此整个问题告一段落,我当时只是学习时,好奇做了个例子,没想到扯出了这么多的内容,难受了一天多,不过我赚了,收获了很多知识。果然时间是公平的,我一个后端和专门浸营前端的基础差了很多。

3.我基本不会很细的去看文档,这就会留下很多死角,这会导致一个问题,就是出现了我认知外的问题,我根本不明白问题根本是在哪里,自然也不知道往哪个方向去寻找真相。其实到最后,我这个问题不过是”js中object的比较是怎么样的“。最后再次谢谢大家,明天还要上班,必须要睡了,这个点已经快神智不清了?…

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题