组件化和 React
一,对组件化的理解
1,组件的封装
-视图
-数据
-变化逻辑(数据驱动视图变化)
例:
import React, { Component } from 'react'; import List from './list/index.js'; import Input from './input/index.js'; class Todo extends Component { constructor(props) { // 数据 super(props) this.state = {//保存当前组件的变量 list:[] } } render() { return ( // 视图 <div> <Input addTitle={this.addTitle.bind(this)}/> <List data={this.state.list} /> <List data={[1,2,3]} /> </div> ) } // 变化逻辑 addTitle(title) { const currentList = this.state.list; this.setState({ list:currentList.concat(title) }) } } export default Todo
2,组件的复用
-props传递
-复用
例:
import React, { Component } from 'react'; class List extends Component { constructor(props) { super(props) } render(){ const list = this.props.data; //props传递 return ( <ul> { list.map((item,index) => { return <li key={index}>{item}</li> }) } </ul> ) } } export default List
二,JSX本质
1,JSX语法:
例:
// html形式 // 引入JS变量和表达式 // if...else... // 循环 // style和className // 事件 render() { const name = '12'; const show = true; const list = [1,2,3,4,5]; const styleConfig = { fontSize:'40px', color:'blue' } return ( <div className='container'> {/* <Todo /> */} <p>{name}</p> <p>{name ? 1 : 0}</p> <p>{(name === '12').toString()}</p> <p>{name || 'lisi'}</p> {show ? <img src='./logo.svg' /> : ''} <ul> {list.map(function(item,index) { return <li key={index}>{item}</li> })} </ul> <p style={styleConfig}>this is a p</p> <p style={{fontSize:'40px',color:'blue'}}>this is a p</p> </div> );
2,JSX解析成JS:
JSX语法根本无法被浏览器所解析,那么它如何在浏览器运行?-转换成JS运行
思考:为何react组件中,React 没有显示地使用,但却必须要引入?(去掉的话会报错) 'React' must be in scope when using JSX
import React, {
Component
} from 'react';
看个例子:
// JSX代码 var profile = <div> <img src="avatar.png" className="profile" /> <h3>{[user.firstName,user.lastName].join('')}</h3> </div>;
解析结果:
// 解析结果 有没有跟vdom的h函数、vue的_c函数很像?yes! var profile = React.createElement("div",null, React.createElement("img",{src:"avatar.png",className:"profile"}), React.createElement("h3",null,[user.firstName,user.lastName].join('')), );
从上面得知,我们所写的JSX代码,都是需要通过 React.createElement 函数来解析
ReactElement createElement( string/ReactClass type, [object props], [children ...] )
(类似 vdom 的 h 函数、vue 的 _c 函数)例:
React.createElement('div',{id:'div1'},child1,child2,child3)
React.createElement('div',{id:'div1'},[...])
再看:
render(){ const list = this.props.data; return ( <ul> { list.map((item,index) => { return <li key={index}>{item}</li> }) } </ul> ) }
解析如下:
function render() { const list = this.props.data; return React.createElement( 'ul', null, list.map((item,index) => { return React.createElement( 'li', {key:index}, item ) }) ) }
JSX的写法大大降低了学习成本和编码工作量
同时,JSX也会增加debug成本
3,独立的标准
JSX 是 React 引入的,但不是 React 独有的,已经是一个标准
React 已经将它作为一个独立标准开放,其他项目也可以使用
React.createElement 是可以自定义修改的
说明:本身功能已经完备;和其他标准兼容和扩展性没问题
使用插件编译jsx代码,测试:
新建文件夹 jsx-test
mkdir jsx-test
进入文件夹
cd jsx-test
初始化:
npm init
新建文件 demo.jsx
touch demo.jsx
全局安装babel
sudo npm i babel -g
安装依赖插件
npm install --sav-dev babel-plugin-transform-react-jsx
创建.babelrc文件,配置:
{ "plugins":["transform-react-jsx"] }
babel编译命令:
babel --plugins transform-react-jsx demo.jsx
demo.jsx代码如下:
class Input extends Component { constructor(props) { super(props) this.state = { title:'' } } render(){ return ( <div> <input value={this.state.title} onChange={this.changeHandle.bind(this)} /> <button onClick={this.clickHandle.bind(this)}>submit</button> </div> ) } changeHandle(event){ this.setState({ title:event.target.value }) } }
编译结果:
class Input extends Component { constructor(props) { super(props); this.state = { title: '' }; } render() { return React.createElement( 'div', null, React.createElement('input', { value: this.state.title, onChange: this.changeHandle.bind(this) }), React.createElement( 'button', { onClick: this.clickHandle.bind(this) }, 'submit' ) ); } changeHandle(event) { this.setState({ title: event.target.value }); } }
尝试把 React.createElement 改成 h 在文件顶部加入
/* @jsx h */
运行编译如下:
class Input extends Component { constructor(props) { super(props); this.state = { title: '' }; } render() { return h( 'div', null, h('input', { value: this.state.title, onChange: this.changeHandle.bind(this) }), h( 'button', { onClick: this.clickHandle.bind(this) }, 'submit' ) ); } changeHandle(event) { this.setState({ title: event.target.value }); } }
总结:
js语法(标签,js表达式,判断,循环,事件绑定)
JSX其实是语法糖(微创新),需被解析成JS才能运行,React.createElement
已经成为独立的标准
三,JSX 和 vdom 关系
1,分析:为何需要vdom
vdom是 React 初次推广开来的,结合 JSX
JSX 就是模版,最终要渲染成 html
初次渲染 + 修改 state 后的 re-render
正好符合 vdom 的应用场景
2,React.createElement 和 h(本质一样)
所有的JSX都要解析成JS,执行最终返回vnode
3,何时 patch ?
(1),初次渲染 ReactDOM.render(<App />, container),会触发 patch(container,vnode)
例:
ReactDOM.render(<App />, document.getElementById('root'));
(2),re-render - setState,会触发 patch(vnode,newVnode)
// patch(vnode,newVnode) this.setState({ list:currentList.concat(title) })
4,自定义组件的解析
'div',直接渲染<div>即可,vdom可以做到
Input和List等自定义组件(class),vdom默认不认识
因此Input和List定义的时候必须声明render函数
根据props初始化实例,然后执行实例的render函数
render函数返回的还是vnode对象
var list = new List({ data: this.state.list }); var vnode = list.render();
例(demo.jsx)
import List from './list/index.js'; import Input from './input/index.js'; function render() { return ( // 视图 <div> <p> this is demo </p> <Input addTitle={this.addTitle.bind(this)}/> <List data={this.state.list} /> <List data={[1,2,3]} /> </div> ) }
解析如下:
import List from './list/index.js'; import Input from './input/index.js'; function render() { return ( // 视图 React.createElement( 'div', null, React.createElement( 'p', null, ' this is demo ' ), React.createElement(Input, { addTitle: this.addTitle.bind(this) }), React.createElement(List, { data: this.state.list }), React.createElement(List, { data: [1, 2, 3] }) ) ); }
总结:
为何需要vdom:JSX需要渲染成html,数据驱动视图
React.createElement 和 h,都生成vnode (React.createElement 既可以创建 html 默认的标签-字符串形式,可以创建自定义组件名称)
何时patch:ReactDOM.render(...) 和 setState()
自定义组件的解析:初始化实例,然后执行render
四,说下 setState 的过程 ( setState 核心函数)
1,setState 的异步
addTitle(title) { const currentList = this.state.list; // patch(vnode,newVnode) console.log(this.state.list); //[1,2] this.setState({ list: currentList.concat(title) //3 }) console.log(this.state.list); //[1,2] 因为上面的setState是异步操作,所以这里仍旧是[1,2] }
原因:
01,可能会一次执行多次 setState
02,你无法规定、限制用户如何使用 setState
03,没必要每次 setState 都重新渲染,考虑性能
04,即便是每次重新渲染,用户也看不到中间的效果(js执行和DOM渲染是单线程的)
05,只看到最后的结果即可
addTitle(title) { const currentList = this.state.list; // 初次想增加 title this.setState({ list: currentList.concat(title) }) // 改变注意,想增加 title + 1 this.setState({ list: currentList.concat(title + 1) }) // 又改变注意,想增加 title + 2 this.setState({ list: currentList.concat(title + 2) }) }
用户操作后,只会看到最后的结果,即 list: currentList.concat(title + 2)
2,vue 修改属性也是异步
01,效果、原因和 setState 一样
02,对比记忆,印象深刻
03,复习下vue 的实现流程
第一步:解析模版成render函数
第二步:响应式开始监听
第三步:首次渲染,显示页面,且绑定依赖
第四步:data 属性变化,触发 rerender ( set中执行updateComponent 是异步的)
3,setState 的过程
01,每个组件实例,都有 renderComponent 方法 (在Component 组件中定义的)
02,执行 renderComponent 会重新执行实例的 render )
03,render 函数返回 newVnode,然后拿到 preVnode (上次执行的 newVnode)
04,执行 patch(preVnode,newVnode)
addTitle(title) { const currentList = this.state.list; // patch(vnode,newVnode) console.log(this.state.list); //[1,2] this.setState({ list: currentList.concat(title) //3 },() => { console.log(this.state.list); //这里能拿到实时结果 [1,2,3] // this.renderComponent() }) console.log(this.state.list); //[1,2] 因为上面的setState是异步操作,所以这里仍旧是[1,2] }
模拟 Component
class Component { constructor(props) { } renderComponent() { const preVnode = this._vnode; const newVnode = this.render(); patch(preVnode,newVnode) this._vnode = newVnode; } }
五,对 React 和 vue 的认识
国内使用,首推vue,文档更易读、易学,社区够大
如果团队水平较高,推荐使用React,组件化和JSX做的更好
1,两者的本质区别
vue-本质是MVVM框架,由MVC发展而来
React-本质是前端组件化框架,由后端组件化发展而来
但这并不妨碍它们两者都能实现相同的功能
2,模版的区别:
vue-使用模版(最初由 angular 提出)
React-使用JSX
模版语法上,更倾向于JSX
模版分离上,更倾向于Vue
3,组件化的区别:
React本身就是组件化,做的更彻底
vue也支持组件化,不过是在MVVM上的扩展
4,两者共同点
都支持组件化
都是数据驱动视图
六,总结:
组件化的理解:
1,组件的封装:封装视图,数据,变化逻辑
2,组件的复用:props传递,复用
JSX本质:
1,语法
2,语法糖,需被解析成JS才能运行
3,JSX是独立的标准,可被其他项目使用
JSX和vdom的关系
1,为何需要vdom:JSX需要渲染成html,还有rerender
2,React.createElement 和 h,都生成vnode
3,何时patch:ReactDOM.render() 和 setState
4,自定义组件的解析:初始化实例,然后执行render
setState过程:
异步:效果、原因 (vue修改属性也是异步,效果、原因)
setState的过程:最终走到 patch(preVnode,newVnode)
附:用 React 实现 todo-list
创建 React 开发环境
全局安装
sudo npm i create-react-app -g --registry=https://registry.npm.taobao.org
运行
create-react-app react-rest
成功之后:
Inside that directory, you can run several commands: yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-rest yarn start Happy hacking!
进入文件夹
cd react-rest
运行
yarn start
实现todo组件
import React, { Component } from 'react'; import List from './list/index.js'; import Input from './input/index.js'; class Todo extends Component { constructor(props) { super(props) this.state = {//保存当前组件的变量 list:[] } } render() { return ( <div> <Input addTitle={this.addTitle.bind(this)}/> <List data={this.state.list} /> </div> ) } addTitle(title) { const currentList = this.state.list; this.setState({ list:currentList.concat(title) }) } } export default Todo
todo中使用的Input组件:
import React, { Component } from 'react'; class Input extends Component { constructor(props) { super(props) this.state = { title:'' } } render(){ return ( <div> <input value={this.state.title} onChange={this.changeHandle.bind(this)} /> <button onClick={this.clickHandle.bind(this)}>submit</button> </div> ) } changeHandle(event){ this.setState({ title:event.target.value }) } clickHandle(){ const title = this.state.title; // 把title添加进列表 const addTitle = this.props.addTitle; addTitle(title);//重点 this.setState({ title:'' }) } } export default Input
todo中使用的List组件
import React, { Component } from 'react'; class List extends Component { constructor(props) { super(props) } render(){ const list = this.props.data;//list组建期望别人传入的列表 return ( <ul> { list.map((item,index) => { return <li key={index}>{item}</li> }) } </ul> ) } } export default List
最后,修改App.js
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; import Todo from './components/todo/index.js' class App extends Component { render() { return ( <div> <Todo /> </div> ); } } export default App;
附:
react 父子组件互相通信
1,父组件向子组件传递 在引用子组件的时候传递,相当于一个属性
<Input addTitle={this.addTitle.bind(this)} /> <List data={this.state.list} />
2,子组件向父组件传递 子组件通过 调用父组件传递到子组件的方法 向父组件传递消息的。
class Input extends Component { constructor(props) { super(props) this.state = { title:'' } // console.log(this.props) } render(){ return ( <div> <input value={this.state.title} onChange={this.changeHandle.bind(this)} /> <button onClick={this.clickHandle.bind(this)}>submit</button> </div> ) } changeHandle(event){ this.setState({ title:event.target.value }) } clickHandle(){ const title = this.state.title; // 把title添加进列表 const addTitle = this.props.addTitle; addTitle(title);//重点 调用父组件方法 this.setState({ title:'' }) } }
附:
React:Rethinking Web App Development at Facebook 只负责view层,不是完整的MVVM框架
React 特点:轻,组件化开发,高度可重用
React应用场景:
复杂场景下的高性能
重用组件库,组件组合
React组件的生命周期和事件处理
React Components Lifecycle
Mounted: React Components 被render解析,生成对应的DOM节点,并被插入浏览器的DOM结构的一个过程( React.renderComponent() )
Update:一个 mounted 的 React Components 被重新render 的过程 (setState() 或者 setProps() ----> render)
Unmounted:一个mounted的 React Components 对应的 DOM 节点被从DOM结构中移除的过程
每一个状态 React 都封装了对应的 hook 函数
未完待续...