课堂目标
- 掌握第三⽅组件正确使⽤⽅式
- 能设计并实现⾃⼰的组件
- 了解常⻅组件优化技术
知识要点
- 使⽤antd
- 设计并实现表单控件
- 实现弹窗类组件
- 实现树组件
- 使⽤PureComponent、memo
资源
- umi
- ant design
快速开始
Create React App 中文文档
npx create-react-app my-app cd my-app npm start
使⽤第三⽅组件
不必eject,直接安装: npm install antd --save
范例:试⽤ ant-design组件库
import React, { Component } from 'react' import Button from 'antd/lib/button' import 'antd/dist/antd.css' class App extends Component { render() { return ( <div className="App"> <Button type="primary">Button</Button> </div> ) } } export default App
配置按需加载
安装react-app-rewired取代react-scripts,可以扩展webpack的配置 ,类似vue.confifig.js
npm install react-app-rewired customize-cra babel-plugin-import -D
根⽬录创建config-overrides.js
const { override, fixBabelImports } = require('customize-cra') module.exports = override( fixBabelImports('import', { //antd按需加载 libraryName: 'antd', libraryDirectory: 'es', style: 'css', }) ) //修改package.json "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },
⽀持装饰器配置
npm install -D @babel/plugin-proposal-decorators
const { addDecoratorsLegacy } = require('customize-cra') module.exports = override( //..., addDecoratorsLegacy() //配置装饰器 )
HocPage.js【按需加载和实现装饰器之后的⻚⾯如下】
import React, { Component } from 'react' import { Button } from 'antd' const foo = (Cmp) => (props) => { return ( <div className="border"> <Cmp {...props} /> </div> ) } const foo2 = (Cmp) => (props) => { return ( <div className="border" style={
{ border: 'solid 1px red' }}> <Cmp {...props} /> </div> ) } @foo @foo2 class Child extends Component { render() { return <div className="border">child</div> } } /* function Child(props) { return <div className="border">child</div>; } */ @foo2 class HocPage extends Component { render() { // const Foo = foo2(foo(Child)); return ( <div> <h1>HocPage</h1> <Child /> <Button type="dashed">click</Button> </div> ) } } export default HocPage
表单组件设计思路
表单组件设计思路:
- 表单组件要求实现数据收集、校验、提交等特性,可通过⾼阶组件扩展
- ⾼阶组件给表单组件传递⼀个input组件包装函数接管其输⼊事件并统⼀管理表单数据
- ⾼阶组件给表单组件传递⼀个校验函数使其具备数据校验功能
antd表单试⽤
import React, { Component } from 'react' import { Form, Input, Icon, Button } from 'antd' const FormItem = Form.Item //校验规则 const nameRules = { required: true, message: 'please input your name' } const passwordRules = { required: true, message: 'please input your password' } @Form.create() class FormPageDecorators extends Component { handleSubmit = () => { /* const { getFieldsValue, getFieldValue } = this.props.form; console.log("submit", getFieldsValue()); */ const { validateFields } = this.props.form validateFields((err, values) => { if (err) { console.log('err', err) } else { console.log('submit', values) } }) } render() { const { getFieldDecorator } = this.props.form // console.log(this.props.form); return ( <div> <h1>FormPageDecorators</h1> <Form> <FormItem label="姓名"> {getFieldDecorator('name', { rules: [nameRules] })( <Input prefix={<Icon type="user" />} /> )} </FormItem> <FormItem label="密码"> {getFieldDecorator('password', { rules: [passwordRules], })( <Input type="password" prefix={<Icon type="lock" />} /> )} </FormItem> <FormItem label="姓名"> <Button type="primary" onClick={this.handleSubmit}> 提交 </Button> </FormItem> </Form> </div> ) } } export default FormPageDecorators // export default Form.create()(FormPageDecorators);
表单组件-具体实现
表单基本结构,创建MyFormPage.js
import React, { Component } from 'react' import kFormCreate from '../../components/kFormCreate' const nameRules = { required: true, message: 'please input your name!' } const passwordRules = { required: true, message: 'please input your password!', } class MyFormPage extends Component { handleSubmit = () => { const { getFieldValue } = this.props const res = { name: getFieldValue('name'), password: getFieldValue('password'), } console.log('hah', res) } handleSubmit2 = () => { // 加⼊校验 const { validateFields } = this.props validateFields((err, values) => { if (err) { console.log('validateFields', err) } else { console.log('submit', values) } }) } render() { const { getFieldDecorator } = this.props return ( <div> <h1>MyFormPage</h1> <div> {getFieldDecorator('name', { rules: [nameRules] })( <input type="text" /> )} {getFieldDecorator('password', [nameRules])( <input type="password" /> )} </div> <button onClick={this.handleSubmit2}>submit</button> </div> ) } } export default kFormCreate(MyFormPage)
⾼阶组件kFormCreate:扩展现有表单,./components/KFormTest.js
import React, { Component } from 'react' export default function kFormCreate(Cmp) { return class extends Component { constructor(props) { super(props) this.options = {} //各字段选项 this.state = {} //各字段值 } handleChange = (e) => { let { name, value } = e.target this.setState({ [name]: value }) } getFieldValue = (field) => { return this.state[field] } validateFields = (callback) => { const res = { ...this.state } const err = [] for (let i in this.options) { if (res[i] === undefined) { err.push({ [i]: 'error' }) } } if (err.length > 0) { callback(err, res) } else { callback(undefined, res) } } getFieldDecorator = (field, option) => { this.options[field] = option return (InputCmp) => ( <div> { // 由React.createElement⽣成的元素不能修改,需要克隆⼀份再扩展 React.cloneElement(InputCmp, { name: field, value: this.state[field] || '', //控件值 onChange: this.handleChange, //控件change事件处理 }) } </div> ) } render() { return ( <div className="border"> <Cmp {...this.props} getFieldDecorator={this.getFieldDecorator} getFieldValue={this.getFieldValue} validateFields={this.validateFields} /> </div> ) } } }
antd表单试⽤
弹窗类组件-设计思路
弹窗类组件的要求弹窗内容在A处声明,却在B处展示。react中相当于弹窗内容看起来被render到⼀个组件⾥⾯去,实际改变的是⽹⻚上另⼀处的DOM结构,这个显然不符合正常逻辑。但是通过使⽤框架提供的特定API创建组件实例并指定挂载⽬标仍可完成任务。
// 常⻅⽤法如下:Dialog在当前组件声明,但是却在body中另⼀个div中显示 ;<div class="foo"> <div> ... </div> {needDialog && ( <Dialog> <header>Any Header</header> <section>Any content</section> </Dialog> )} </div>
弹窗类组件-具体实现-⽅案1:Portal
传送⻔,react v16之后出现的portal可以实现内容传送功能。
范例:Dialog组件
// Diallog.js import React, { Component } from 'react' import { createPortal } from 'react-dom' import './index.scss' export default class Diallog extends Component { constructor(props) { super(props) const doc = window.document this.node = doc.createElement('div') doc.body.appendChild(this.node) } componentWillUnmount() { window.document.body.removeChild(this.node) } render() { const { hideDialog } = this.props return createPortal( <div className="dialog"> {this.props.children} {typeof hideDialog === 'function' && ( <button onClick={hideDialog}>关掉弹窗</button> )} </div>, this.node ) } }
Diallog/index.scss
.dialog { position: absolute; top: 0; right: 0; bottom: 0; left: 0; line-height: 30px; width: 400px; height: 300px; transform: translate(50%, 50%); border: solid 1px gray; text-align: center; }
弹窗类组件-具体实现-⽅案2:unstable_renderSubtreeIntoContainer
在v16之前,实现“传送⻔”,要⽤到react中两个秘⽽不宣的React API
export class Dialog2 extends React.Component { render() { return null } componentDidMount() { const doc = window.document this.node = doc.createElement('div') doc.body.appendChild(this.node) this.createPortal(this.props) } componentDidUpdate() { this.createPortal(this.props) } componentWillUnmount() { unmountComponentAtNode(this.node) window.document.body.removeChild(this.node) } createPortal(props) { unstable_renderSubtreeIntoContainer( this, //当前组件 <div className="dialog">{props.children}</div>, // 塞进传送⻔的JSX this.node // 传送⻔另⼀端的DOM node ) } }
总结⼀下:
- Dialog什么都不给⾃⼰画,render返回⼀个null就够了;
- 它做得事情是通过调⽤createPortal把要画的东⻄画在DOM树上另⼀个⻆落。
树形组件-设计思路
递归:⾃⼰调⽤⾃⼰
如计算f(n)=f(n-1)*n; n>0, f(1)=1
function foo(n) { return n === 1 ? 1 : n * foo(n - 1) }
react中实现递归组件更加纯粹,就是组件递归渲染即可。假设我们的节点组件是TreeNode,它的render中只要发现当前节点拥有⼦节点就要继续渲染⾃⼰。节点的打开状态可以通过给组件⼀个open状态来维护。
树形组件-具体实现
TreeNode.js
import React, { Component } from 'react' import TreeNode from '../../components/TreeNode' //数据源 const treeData = { key: 0, //标识唯⼀性 title: '全国', //节点名称显示 children: [ //⼦节点数组 { key: 6, title: '北⽅区域', children: [ { key: 1, title: '⿊⻰江省', children: [ { key: 6, title: '哈尔滨', }, ], }, { key: 2, title: '北京', }, ], }, { key: 3, title: '南⽅区域', children: [ { key: 4, title: '上海', }, { key: 5, title: '深圳', }, ], }, ], } export default class TreePage extends Component { render() { return ( <div> <h1>TreePage</h1> <TreeNode data={treeData} /> </div> ) } }
TreeNode.js
import React, { Component } from 'react' import classnames from 'classnames' //先安装下npm install classnames export default class TreeNode extends Component { constructor(props) { super(props) this.state = { expanded: false, } } handleExpanded = () => { this.setState({ expanded: !this.state.expanded, }) } render() { const { title, children } = this.props.data const { expanded } = this.state const hasChildren = children && children.length > 0 return ( <div> <div className="nodeInner" onClick={this.handleExpanded}> {hasChildren && ( <i className={classnames( 'tri', expanded ? 'tri-open' : 'tri-close' )} ></i> )} <span>{title}</span> 常⻅组件优化技术 定制组件的shouldComponentUpdate钩⼦ 范例:通过shouldComponentUpdate优化组件 </div> {expanded && hasChildren && ( <div className="children"> {children.map((item) => { return <TreeNode key={item.key} data={item} /> })} </div> )} </div> ) } }
树组件css
.nodeInner { cursor: pointer; } .children { margin-left: 20px; } .tri { width: 20px; height: 20px; margin-right: 2px; padding-right: 4px; } .tri-close:after, .tri-open:after { content: ""; display: inline-block; width: 0; height: 0; border-top: 6px solid transparent; border-left: 8px solid black; border-bottom: 6px solid transparent; } .tri-open:after { transform: rotate(90deg); }
常⻅组件优化技术-定制组件的shouldComponentUpdate钩⼦
范例:通过shouldComponentUpdate优化组件
import React, { Component } from 'react' export default class CommentList extends Component { constructor(props) { super(props) this.state = { comments: [] } } componentDidMount() { setInterval(() => { this.setState({ comments: [ { author: '⼩明', body: '这是⼩明写的⽂章', }, { author: '⼩红', body: '这是⼩红写的⽂章', }, ], }) }, 1000) } render() { const { comments } = this.state return ( <div> <h1>CommentList</h1> {comments.map((c, i) => { return <Comment key={i} data={c} /> })} </div> ) } } class Comment extends Component { shouldComponentUpdate(nextProps, nextState) { const { author, body } = nextProps.data const { author: nowAuthor, body: nowBody } = this.props.data if (body === nowBody && author === nowAuthor) { return false //如果不执⾏这⾥,将会多次render } return true } render() { console.log('hah') const { body, author } = this.props.data return ( <div> <p>作者: {author}</p> <p>正⽂:{body}</p> <p>---------------------------------</p> </div> ) } }
常⻅组件优化技术-PureComponent
定制了shouldComponentUpdate后的Component
import React, { Component, PureComponent } from 'react' export default class PuerComponentPage extends PureComponent { constructor(props) { super(props) this.state = { counter: 0, obj: { num: 100, }, } } setCounter = () => { this.setState({ counter: 1, obj: { num: 200, }, }) console.log('setCounter') } render() { console.log('render') const { counter, obj } = this.state return ( <div> <button onClick={this.setCounter}>setCounter</button> <div>counter: {counter}</div> <div>obj.num: {obj.num}</div> </div> ) } }
缺点是必须要⽤class形式,⽽且要注意是浅⽐较
常⻅组件优化技术-React.memo
React.memo(...) 是React v16.6引进来的新属性。它的作⽤和 React.PureComponent 类似,是⽤来控制函数组件的重新渲染的。 React.memo(...) 其实就是函数组件的 React.PureComponent 。
import React, { Component, PureComponent, memo } from 'react' export default class MemoPage extends Component { constructor(props) { super(props) this.state = { counter: 0, obj: { num: -1 }, } } setCounter = () => { this.setState({ counter: 1 /* , obj: { num: 100, }, */, }) } render() { const { counter } = this.state return ( <div> <h1>MemoPage</h1> <button onClick={this.setCounter}>按钮</button> {/* <PuerCounter counter={counter} obj={obj} /> */} <PuerCounter counter={counter} /> </div> ) } } const PuerCounter = memo((props) => { console.log('render') return <div>{props.counter}</div> })
作业
实现下图:提示:可以使⽤antd的 Card、Input、Tree、Button、Form、Table
React组件化2
- 课堂⽬标
- 知识要点
- 资源
- 知识点
- 快速开始
- 使⽤第三⽅组件
- 配置按需加载
- 表单组件设计与实现
- antd表单试⽤
- 表单组件设计思路
- 表单组件实现
- 弹窗类组件设计与实现
- 设计思路
- 具体实现
- ⽅案1:Portal
- ⽅案2:unstable_renderSubtreeIntoContainer
- 树形组件设计与实现
- 设计思路
- 实现
- 常⻅组件优化技术
- 定制组件的shouldComponentUpdate钩⼦
- PureComponent
- React.memo
- 作业
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/yd-react-native/11103.html