props
对象并返回了一个 React 元素function Welcome(props) {return <h1>Hello, {props.name}</h1>}
class Welcome extends React.Component {render(){return <h1>Hello, {this.props.name}</h1>}}
props
class Panel extends Component {render (){let { header ,body} = this.propsreturn (<div className="container"><div className="panel-default panel"><Header header={header}></Header><Body body={body}/></div></.div>)}}class Body extends Component {render(){return <div className="panel-body">{this.props.body}</div>}}class Header extends Component {render(){return <div classNmae="panel-heading"><this.props.header></div>}}let data= {header:'123',body: '456'}ReactDom.render(<Panel {...data}/>,window.root)
props
纯函数
没有改变它自己的输入值,当传入的值相同时,总是返回相同的结果// 纯函数function sum(a, b) {return a + b}// 非纯函数function withdraw(account, amount) {account.total -= amount}
import PropsTypes from 'prop-types'MyComponent.propTypes = {// 你可以将属性声明为js原生类型,默认情况下 这些属性都是可选的optionalArray: PropTypes.array,optionalBool:PropTypes.bool,optionalFunc:PropTyeps.number,potionalNumber:PropTypes.object,optionalObject:PropTypes.string,optionalSymbol:PropTypes.symbol,// 任何可被渲染的元素(包含数字,字符串、元素或数组或Fragment)也包含这些类型optionalNode:PropTypes.node,// 一个React元素optionalElement:PropTypes.element,// 你也可以声明prop为类的实例,这里使用js的instanceof操作符optionalMessage:PropTypes.instancOf(Message),//你可以让你的prop只能是特定的值,指定它为枚举类型optionalEnum:PropTypes.oneOf(['News','Photos'])//一个对象可以是几种类型中的任意一个类型optionalUnion:PropTypes.oneOfType([PropTypes.string,PropTypes.number,PropTypes.instanceOf(Message)]),//可以指定一个数组由某一类型的元素组成optionalArrayOf:PropTypes.arrayOf(PropTypes.number),//可以指定一个对象由某一类型的值组成optionalObjectOf:PropsTypes.objectOf(PropTypes.number),//可以指定一个对象由特定的类型值组成optionalObjectWithShape:PropTypes.shape({color:PropTypes.string,fontSize:PropTyeps.number})// 你可以在任何PropTypes属性后面加上`isRequired`,确保这个prop没有被提供时,会打印警告信息requiredFunc:PropTypes.func.isRequired,//任意类型的数据requiredAny:PropTypes.any.isRequired,//你可以指定一个自定义验证器。它在验证失败时应返回一个Error对象;请不要使用`console.warn`或抛出异常,因为这在`oneOfType`中不会起作用customProp:function(props,propName,componentName){if(!/matchme/.test(props[propName])){return new Error('Invalid prop`'+propName+'`supplied to'+'`'+componentName+'`. Validation failed .')}},// 你也可以提供一个自定以的`arrayOf` 或`objectOf`验证器;它应该在验证失败时返回一个Error对象;验证器将验证数组对象中的每个值。验证器的前两个参数;第一个是数组或对象本身,第二个是他们当前的键customArrayProp: PropTypes.arrayOf(function(propValue,keyi,componentName,location,propFullName)){if(!/matchme/.test(propValue[key])){return new Error('Invalid prop`'+propName+'`supplied to'+'`'+componentName+'`. Validation failed .')}}}
import React from "react"import ReactDOM from "react-dom"import PropTypes from "prop-types"class Person extends React.Component {static defaultProps = {name: "Stranger",}static propTypes = {name: PropTypes.string.isRequired,age: PropTypes.number.isRequired,gender: PropTypes.oneOf(["male", "famale"]),hobby: PropTypes.array,postion: PropTypes.shape({x: PropTypes.number,y: PropTypes.number,}),}render() {let { name, age, gender, hobby, position } = this.propsreturn (<table><thead><tr><td>姓名</td><td>年龄</td><td>性别</td><td>爱好</td><td>位置</td></tr></thead><tbody><tr><td>{name}</td><td>{age}</td><td>{gender}</td><td>{hobby.join(",")}</td><td>{position.x + " " + position.y}</td></tr></tbody></table>)}}let person = {age: 100,gender: "male",hobby: ["basketball", "football"],position: { x: 10, y: 10 },}ReactDOM.render(<Person {...person} />, document.getElementById("root"))
import React from "./react"import ReactDOM from "./react-dom"class Welcome extends React.Component {render() {return React.createElement("h1", { className: "title" }, this.props.title)}}let element = React.createElement(Welcome, { title: "标题" })ReactDOM.render(element, document.getElementById("root"))
import createElement from "./element"class Component {static isReactComponent = trueconstructor(props) {this.props = props}}export default {createElement,Component,}
const ReactElement = function (type, props) {const element = {type: type,props: props,}return element}function createElement(type, config, children) {let propNameconst props = {}for (propName in config) {props[propName] = config[propName]}const childrenLength = arguments.length - 2if (childrenLength === 1) {props.children = children} else if (childrenLength > 1) {props.children = Array.prototype.slice.call(arguments, 2)}return ReactElement(type, props)}export default createElement
function render(element, container) {if (typeof element == "string") {return container.appendChild(document.createTextNode(element))}let type, propstype = element.typeprops = element.propsif (type.isReactComponent) {element = new type(props).render()type = element.typeprops = element.props}let domElement = document.createElement(type)for (let propName in props) {if (propName === "children") {let children = props[propName]children = Array.isArray(children) ? children : [children]children.forEach((child) => render(child, domElement))} else if (propName === "style") {let styleObj = props[propName]let cssText = Object.keys(styleObj).map((attr) => {return `${attr.replace(/[A-Z]/g, function () {return "-" + arguments[1]})}:${styleObj[attr]}`}).join(";")domElement.style.cssText = cssText} else {domElement.setAttribute(propName, props[propName])}container.appendChild(domElement)}}export default { render }
setState
import React from 'react'import ReactDOM from 'react-dom'class Clock extends React.Component {constructor(props){super(props){this.state = {date: new Date()}}componentDidMount(){this.timerID = setInterval(() => this.tick(),1000)}componentWillUnmount(){clearInterval(this.timerID)}tick(){this.setState({date: new Date()})}render(){return (<div><h1> Hello, word!</h1><h2>It is { this.state.date.toLocaleTimeString()}</h2></div>)}}}ReactDOM.render(<Clock>,document.getElementById('root'))
this.state
赋值的地方import React from 'react'import ReactDOM from 'react-dom'class Counter extends React.Component {constructor(props){super(props){this.state = {number: 0}}}componentDidMount(){this.timerID = setInterval(() => {this.state.number = this.state.number + 1},1000)}componentWillUnmount(){clearInterval(this.timerID)}render(){return (<div><p>{this.state.number}</p></div>)}}ReactDOM.render(<Counter />,document.getElementById('root'))
import React from "react"import ReactDOM from "react-dom"class Counter extends React.Component {constructor(props) {super(props)this.state = {number: 0,}}handleClick = () => {this.setState((state) => ({ number: state.number + 1 }))this.setState((state) => ({ number: state.number + 1 }))}render() {return (<div><p>{this.state.number}</p><button onClick={this.handleClick}>+</button></div>)}}ReactDOM.render(<Counter />, document.getElementById("root"))
import React from "react"import ReactDOM from "react-dom"class Counter extends React.Component {constructor(props) {super(props)this.state = {name: "zhufeng",number: 0,}}handleClick = () => {this.setState((state) => ({ number: state.number + 1 }))this.setState((state) => ({ numbner: state.number + 1 }))}render() {return (<div><p>{this.state.name}:{this.state.number}</p><button onClick={this.handleClick}>+</button></div>)}}ReactDOM.render(<Counter />, document.getElementById("root"))
import React from 'react'import ReactDOM from 'react-dom'class Counter extends React.Component{constructor(props){super(props)this.state= {number: 90}}handleClick(()=>{this.setState(state=>({number:state.number+1}))})render(){return (<div style={{border:'1px solid red'}}><p>{this.state.name}:{this.state.number}</p><button onClick={this.handleClick}>+</button><SubCounter number={this.state.number} /></div>)}}class SubCounter extends React.Component {render(){return <div style={{border:'1px solid blue'}}>子计数器:{this.props.number}</div>}}ReactDOM.render(<Counter />,document.getElementById('root'))
false
方式阻止默认行为,你必须显式使用preventDefault
import React from "react"import ReactDOM from "react-dom"class Link extends React.Component {handleClick(e) {e.preventDefault()console.log("The link was clicked .")}render() {return (<a href="http://www.baidu.com" onClick={this.handleClick}>Click me</a>)}}ReactDOM.render(<Link />, document.getElementById("root"))
class LoggingButton extends React.Component {handleClick(){console.log('this is:', this)}handleClcik1=()=>{console.log('this is:',this)}render(){return (<button onClick={event => this.handleClick(event)}>Click me</button>)}}
class LoggingButton extends React.Component {handleClick = (id, event) => {console.log("id", id)}render() {return (<><button onClick={(event) => this.handleClick("1", event)}>Click me</button><button onClick={this.handleClick.bind(this, "1")}>Click me</button></>)}}
首先让我们看一下一个简单的有状态组件:
class Example extends React.Component {constructor(props) {super(props)this.state = {count: 0,}}render() {return (<div><p>You clicked {this.state.count} times</p><button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button></div>)}}
我们来看一下使用 hooks 后的版本
import { useState } from "react"function Example() {const [count, setCount] = useState(0)return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>)}
可以看到,Example 变成了一个函数,但这个函数却又自己的状态,同时它还可以更新自己的状态。这个函数之所以这个牛,就是因为它注入了一个 hook--useState,就是这个 hook 让我们的函数变成了一个有状态的函数。
除了 useState 这个 hook 外,还有很多别的 hook,比如 useEffect 提供了类似于 componentDidMount 等生命周期钩子的功能,useContext 提供了上下文的功能等。
Hooks 本质上就是一类特殊的函数,他们可以为你的函数型组件(function component)注入一些特殊的功能。
我们都知道 react 的核心思想就是,将一个页面拆层一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用 react,你会发现你的项目中实际上有很多 react 组件冗长且难以复用。尤其是那些写成 class 的组件,它们本身包含了状态,所以复用这类组件就变得很麻烦。
之前官方推荐的解决方案就是:渲染属性和高阶组件
渲染属性指的是使用一个值为函数的 prop 来传递需要动态渲染的 nodes 或者组件,如果下面的代码可以看到我们的 DataProvider 组件包含了所有跟状态相关的代码,而 Cat 组件可以是一个单纯的展示型组件,这样一来 DataProvider 就可以单独复用了。
import Cat from "components/cat"class DataProvider extends React.Component {constructor(props) {super(props)this.state = { target: "zbc" }}render() {return <div>{this.props.render(this.state)}</div>}};<DataProvider render={(data) => <Cat target={data.target} />} />
虽然这个模式叫 Render Props,但不是说非用 render 的 props 不可,习惯上大家更长写成下面这种
<DataProvider>{(data) => <Cat target={data.target} />}</DataProvider>
高阶组件这个概念就更好理解了,说白了就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。看下面的代码示例,withUser 函数就是一个高阶组件,它返回了一个新组件,这个组件具有了它提供的获取用户信息的功能。
const withUser = (wrappedComponent) => {const user = sessionStorage.getItem("user")return (props) => <WrappedComponent user={user} {...props} />}const UserPage = (props) => (<div class="user-container"><p>My name is {props.user}!</p></div>)export default withUser(UserPage)
以上这两种模式看上去都挺不错的,很多库也运用了这种模式,比如我们常用的 React Router 但我们仔细看这两种莫斯,会发现他们会增加我们代码的层级关系。最直观的体现,打开 devtool 看看你的组件层级嵌套是不是很夸张。这时候再回头看我们上一节给出的 hooks 列子,是不是简洁多了,没有多余的层级嵌套。把各种想要的功能写成一个一个可复用的自定义 hook,当你的组件想用什么功能时,直接在组件里调用这个 hook 即可。
我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。比如我们需要在 componentDidMount 中发起 ajax 请求获取数据,绑定一些事件监听等等。同时,有时候我们还需要在 componentDidUpdate 做一遍同样的事情。当项目变复杂以后,这一块代码也变得不那么直观。
我们用 class 来创建 react 组件时,还有一件很麻烦的事情,就是 this 的指向问题。为了保证 this 的指向正确,我们要经常写这样的代码:this.handleClick=this.handleClick.bind(this),或者是这样的代码:<button onClick={()=>this.handleClick(e)}>。一旦我们不小心忘了绑定 this,各种 bug 就随之而来,很麻烦。
还有一件让我们很苦恼得到事情。尽可能把你的组件写成无状态组件的形式,因为他们更方便复用,可以独立测试。然而很多时候,我们用 function 写了一个简洁的完美的无状态组件,后来因为需求变动这个组件必须得有自己的 state,我们又得很麻烦的把 function 改为 class。
回到一开始我们用的例子,我们分解来看到底 state hooks 做了什么。
import { useState } from "react"function Example() {const [count, setCount] = useState(0)return (<div><p>You clicked {count} timers</p><button onClick={() => setCount(count + 1)}>Click me</button></div>)}