主要概念
1、 在常规的 React 数据流中,props 是父组件与子组件交互的唯一方式
,要修改子元素,需要用新的 props
去重新渲染子元素
2、 函数组件必须显式传递props
,类组件不需要显式传递但是获取时需要在前边加this(this.props)
3、 提取组件的规则:(1)会被复用的组件要提出来;(2) 当条件渲染的条件过于复杂时;(3) 当map()
中有太多嵌套时
4、 props
是只读的,无论是函数组件还是类组件,都无法修改自身的props
5、 state
是私有的,并且由组件自身控制
6、 存储不参与数据流的内容(如计时器id)
可以自由向该类(组件类本身不是state
)中添加其他字段(不用声明变量,直接通过this.xxx=xxx
添加)
7、 setState
用法:
(1) this.setState({xxx:yyy})
;
(2) this.setState((state,props)=>({xxx:yyy}))
,该函数接收前一个状态值(变化前的state
)作为第 1
个参数, 并将更新后的props
值作为第 2
个参数:
8、 无论作为父组件还是子组件,它都无法获悉另一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。state
不能被自身组件以外的任何组件访问
9、 单向数据流:任何state
始终由某个特定的组件所有,并且从该state
导出的任何数据或ui
只能影响树中“下方”的组件
10、 React
中不能通过 return false
来阻止默认行为,必须明确调用e.preventDefault()
事件冒泡处理: 1、合成事件间,e.stopPropagation()
;2、原生事件
与最外层document
上的事件间,e.nativeEvent.stopImmediatePropagation()
;3、阻止合成事件
与除最外层document上的原生事件
,’判断e.target
’
11、 函数组件获取事件及props
前边不能加this
12、 利用箭头函数传递事件,必须显式的传递事件对象e
13、 如果函数需要通过props
传递给下级组件以便下级组件调用,建议使用在构造函数中.bind(this)
的方法绑定this
14、 属性初始化语法:不用bind(this)
,(构造函数外)xxx=()=>{console.log(this)}
15、 在回调中使用一个箭头函数(不用绑定this
,但是必须显式的传递事件对象e
)
1 <div onClick ={(e) => { this.ccc(e)}}></div >
16、 array.map((item,index)=>{})
列表渲染,“key
”是必须带有的特殊字符串属性
,只要调用map()
须绑定keys
17、 键(keys
)是react
的一个内部映射,不会传递到组件内部(组件内部获取不到keys
)
18、 在React
中,可变状态一般保存在组件的state中
,并且只能通过setState()
更新
19、 “受控组件”:其值由React控制的输入表单元素
20、 任何可变数据都应遵循"单一数据源"
的原则
21、 能从props
获取的数据不建议从state
获取
22、 将父组件的函数通过props
传递给子级,子级通过this.props.xxx
就可以调用父级的函数
23、 组件JSX
标签中的任何内容(包括直接写的文字)都会被传递到组件中,作为children prop
通过{props.children}
获取
24、 组件JSX
标签中的内容,在组件中不使用{props.children}
接收时,是不显示的
25、 组件可以接收任意的props
,可以是原始值、React
元素、函数
26、 Refs
:强制修改子元素。被修改的子元素可以是React
组件实例(类组件的实例),或者是一个DOM
元素,不能是函数组件
27、 使用React.createRef()
创建refs
,通过ref
属性来获得子级元素,使用ref.current
属性对节点进行访问
28、 不受控组件初始值设置:使用defaultValue
而不是value
属性,单选与多选支持defaultChecked
,select
和textarea
支持defaultValue
;
29、 状态提升:同一个数据的变化需要几个不同的组件来反映时,需要提升这个共享的状态到用到它的组件的最近的祖先组件中
30、 状态提升主要在于子组件接收父组件的数据
、子组件怎么调用父组件的函数来改变父组件的数据
,明白这一点以后,就是组件的嵌套
31、 函数组件中定义函数 必须是 : function xxx(){}
32、 React
的编程思想
将 UI
拆解到组件层次结构中
用 React
构建一个静态版本
确定 UI state
(状态) 的最小但能完整展现功能的父级组件
确定哪个组件可变,或者说哪个组件使用这些 state
(状态) 。
添加反向数据流(就是:子组件改变父组件数据,一般通过props
将改变父组件数据的父组件函数传递给子组件)
33、 在JSX
中,label
标签的 for
特性被写作htmlFor
34、 何时使用 Refs
处理focus
、文本选择或者媒体播放
触发强制动画
集成第三方DOM
库
如果可以通过声明式实现,就尽量避免使用 refs
35、 能使用状态提升运用state
改变组件状态,不要使用 refs
来更新组件
36、 当ref
是类组件实例时,可以直接获取实力上的属性及方法
37、 不能在函数式组件上使用 ref
属性,因为它们没有实例,如果需要使用 ref
,就需要将组件转化成 类(class
)组件,就像需要 生命周期方法 或者 state
一样。
38、 但是可以 在函数式组件内部使用 ref
来引用一个 DOM
元素或者 类(class
)组件
39、 能用 createRef
不用回调refs
40、 “回调 ref
”不同于传递 createRef()
创建的 ref
属性,它会传递一个函数。这个函数接受 React
组件的实例或 HTML DOM
元素作为参数,以存储它们并使它们能被其他地方访问
41、 在组件间通过props
传递回调形式的 refs
(父组件获取子组件元素)
42、 类组件 render
函数 return
的只能有一个根元素
43、 回调ref
不需要通过 current
属性访问,直接this.textInput.focus()
;
44、 Ref
转发是一种自动将 ref
通过组件传递给子组件的技术
45、 React.forwardRef((props,ref)=>())
接收props
,ref
作为参数
46、 Ref
转发不限于 DOM
组件。也可以将 refs
转发给类组件实例
(1)创建 ref
this.ref=React.createRef();
(2)绑定ref
1 2 3 <FancyButton ref ={this.ref} > 我是你绑定的ref </FancyButton >
(3)ref
转发
1 2 3 4 5 const FancyButton =React.forwardRef((props,ref )=> ( <button ref={ref} className="FancyButton" > {props.children} </button> ))
47、 凡是用createRef
创建的都需要使用current
属性访问元素
48、 将父组件通过createRef
创建的Ref
通过props
传递给子组件,子组件将传递过来的ref
绑定在其元素上也能实现 ref
转发
49、 高阶组件是一个函数,能够接受一个组件并返回一个新的组件。
50、 代码拆分: 可以帮助你 “懒加载(lazy-load
)” 用户当前需要的东西,这可以显著提高应用程序的性能。
51、 Suspense
:在等待加载时显示一些后备内容 - 例如加载指示符。
52、 React.lazy
目前仅支持默认导出的组件(export default
),如果想要导入的模块使用命名导出,则可以创建一个中间模块,将其重新导出为默认模块。
53、 基于路由的代码拆分:在定义路由是使用代码拆分是最好的
54、 上下文(Context
) 提供了一种通过组件树传递数据
的方法,无需在每个级别手动传递 props
属性。
55、 当一些数据需要在不同的嵌套级别上被许多组件访问时,首先考虑使用 Context
56、 组件是React中代码重用的最小单元
57、 从本质上讲,JSX
只是为 React.createElement(component, props, ...children)
函数提供的语法糖
58、 初始化state
必须在constructor
中,并且调用constructor
后必须使用super(props)
将 props
传递给基础构造函数
59、所有 React 组件都必须是纯函数
,并禁止修改其自身 props
60、“纯函数” :不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果
61、唯一可以分配 (初始化state)this.state 的地方是构造函数construct
62、所有组件都是完全独立的,独立更新
63、通过 JSX
, 传递一个函数作为事件处理程序而不是一个字符串(onClick={}
而不是onClick=""
)
64、通过返回null
实现显示隐藏,从组件的 render
方法返回 null
不会影响组件生命周期方法的触发
65、event.preventDefault()
阻止默认事件
create-react-app
npm
内置create-react-app
脚手架,不用特意安装create-react-app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 npx create-react-app my-app npm init react-app my-app cd my-app npm start npm run build 或者 yarn build npm run eject
JSX
是 JavaScrip
的一种扩展语法
从本质上讲,JSX
只是为 React.createElement(component, props, ...children)
函数提供的语法糖
用{}
在JSX中嵌入表达式
用 花括号{}
可以把任意的 JavaScript
表达式 嵌入到 JSX
中。例如:2 + 2
, user.firstName
, 和 formatName(user)
推荐使用括号将 JSX
包裹起来,可以避免分号自动插入的陷阱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React from 'react' ;import ReactDOM from 'react-dom' ;function formatName (user ) { return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper' , lastName: 'Perez' }; const element = ( <h1> Hello, {formatName(user)}! </h1> ); ReactDOM.render( element, document .getElementById('root' ) );
JSX 也是一个表达式
编译之后,JSX
表达式就变成了常规的 JavaScript
对象
可以在 if
语句或者是 for
循环中使用 JSX
,可以用jsx
给变量赋值、当做参数接收,或者作为函数的返回值
1 2 3 4 5 6 function getGreeting (user ) { if (user) { return <h1 > Hello, {formatName(user)}!</h1 > ; } return <h1 > Hello, Stranger.</h1 > ; }
JSX 指定属性值
使用双引号来指定字符串字面量作为属性值
用花括号嵌入一个 JavaScript
表达式作为属性值
在属性中嵌入 JavaScript
表达式时,不要使用引号将花括号括起来
。
引号
(用于字符串值)或大括号
(用于表达式),不能同时使用于同一个属性
react的属性名应用驼峰命名
,例如:class
变成了 className
,tabindex
变成了 tabIndex
1 2 const element = <div tabIndex ="0" > </div > ;const element = <img src ={user.avatarUrl} > </img > ;
JSX 指定子元素
单标签必须用/>
闭合
JSX
标签可以像html
一样直接包含子元素
1 2 3 4 5 6 const element = ( <div> <h1>Hello!</h1> <h2>Good to see you here.</h2> </div> );
JSX 原理(JSX是一个对象)
JSX
原理是:Babel
编译时调用React.createElement()
,将jsx
编译为React
元素对象(普通json
对象)
下面的两种方法是完全相同的:
1 2 3 4 5 6 7 8 9 10 11 12 13 const element = ( <h1 className="greeting" > Hello, world! </h1> ); const element = React.createElement( 'h1' , {className : 'greeting' }, 'Hello, world!' );
上面两种最后都会是一个对象,我们称之为:**React元素
**
1 2 3 4 5 6 7 8 const element = { type: 'h1' , props: { className: 'greeting' , children: 'Hello, world' } };
对 JSX 类型使用点语法( <MyComponents.DatePicker />
)
可以使用点语法引用一个 React
组件
1 2 3 4 5 6 7 8 9 10 11 import React from 'react' ;const MyComponents = { DatePicker: function DatePicker (props ) { return <div > Imagine a {props.color} datepicker here.</div > ; } } function BlueDatePicker ( ) { return <MyComponents.DatePicker color ="blue" /> ; }
JSX 中的 props(属性)
在 JavaScript
中,if
语句和 for
循环不是表达式,因此不能在 JSX
中直接使用。但你可以将他们放在附近的代码块中
1 2 3 4 5 6 7 8 9 function NumberDescriber (props ) { let description; if (props.number % 2 == 0 ) { description = <strong > even</strong > ; } else { description = <i > odd</i > ; } return <div > {props.number} is an {description} number</div > ; }
props(属性) 默认为 “true”
不建议使用这种写法
如果你没给 prop
(属性) 传值,那么他默认为 true
。下面两个 JSX
表达式是等价的:
1 2 <MyTextBox autocomplete /> <MyTextBox autocomplete={true } />
属性展开
如果有一个 object
类型的 props
,并且希望在 JSX
中传入,可以使用扩展操作符 ...
传入整个 props
对象。这两个组件是等效的:
1 2 3 4 5 6 7 8 function App1 ( ) { return <Greeting firstName ="Ben" lastName ="Hector" /> ; } function App2 ( ) { const props = {firstName : 'Ben' , lastName : 'Hector' }; return <Greeting {...props } /> ; }
也可以挑选组件需要的的指定属性(props
),同时使用展开运算符传递所有其他属性(props
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const Button = props => { const { kind, ...other } = props; const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton" ; return <button className ={className} {...other } /> ; }; const App = () => { return ( <div> <Button kind="primary" onClick={() => console .log("clicked!" )}> Hello World! </Button> </div> ); };
JSX 中的 Children 字符串字面量
开放标签和闭合标签中放入一个字符串,那么 props.children
就是那个字符串
1 2 <MyComponent>Hello world!</MyComponent>
JSX
会删除每行开头和结尾的空格,并且也会删除空行。邻接标签的空行也会被移除,字符串之间的空格会被压缩成一个空格
下面写法效果一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <div > Hello World</div > <div > Hello World </div > <div > Hello World </div > <div > Hello World </div >
JavaScript 表达式作为 Children(子元素)`
通过使用 {}
包裹,你可以将任何的 JavaScript
元素而作为 children
(子元素) 传递
1 2 3 4 5 6 7 8 9 10 11 12 function Item (props ) { return <li > {props.message}</li > ; } function TodoList ( ) { const todos = ['finish doc' , 'submit pr' , 'nag dan to review' ]; return ( <ul> {todos.map((message ) => <Item key ={message} message ={message} /> )} </ul> ); }
1 2 3 function Hello (props ) { return <div > Hello {props.addressee}!</div > ; }
Functions(函数) 作为 Children(子元素) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Repeat (props ) { let items = []; for (let i = 0 ; i < props.numTimes; i++) { items.push(props.children(i)); } return <div > {items}</div > ; } function ListOfTenThings ( ) { return ( <Repeat numTimes={10 }> {(index ) => <div key ={index} > This is item {index} in the list</div > } </Repeat> ); }
Booleans, Null, 和 Undefined 被忽略
false,null,undefined
,和 true
都是有效的的 children
(子元素) 。但是并不会被渲染
在输出中想要渲染 false
,true
,null
或者 undefined
,必须先将其转化为字符串
下面的JSX
表达式渲染效果是相同的:
1 2 3 4 5 6 7 8 9 10 11 <div /> <div > </div > <div > {false}</div > <div > {null}</div > <div > {undefined}</div > <div > {true}</div >
需要注意的是“false
”值,例如数值 0
,仍然会被 React
渲染。例如,这段代码不会按照你预期的发生,因为当 props.messages
是一个空数组时 0
会被打印
1 2 3 4 5 6 7 <div > {props.messages.length && <MessageList messages ={props.messages} /> } </div >
确保 && 之前的表达式总是布尔值
1 2 3 4 5 <div> {props.messages.length > 0 && <MessageList messages={props.messages} /> } </div>
页面中引用本地图片、背景图设置
方法一:
1 <img src ={require( '.. /img /icon1.png ')} alt ="" />
方法二:
1 2 3 4 import search from '../img/search.png' import user from '../img/user.png' <img src ={search} alt ="" /> <img src ={user} alt ="" />
方法三:(不推荐使用)
1 <img src ='http://localhost:3000/my-project/src/img/icon1.png' alt ="" />
背景图片引用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const divStyle = { color: 'red' , backgroundImage: 'url(' + imgUrl + ')' , }; function HelloWorldComponent ( ) { return <div style ={divStyle} > Hello World!</div > ; } import log from './logo.svg' var divStyle={ width:'100px' , height:'100px' , color:'red' , background:`url(${log} )` } const element=( <div style={divStyle}></div> )
元素渲染(ReactDOM.render())
不同于浏览器的 DOM
元素, React 元素是普通的对象
,React DOM
会负责更新 DOM
,以匹配React
元素(使DOM
元素与React
元素保持一致)
1 2 3 4 5 6 7 8 9 10 11 const element = ( <h1>Hello, world</h1> ); ReactDOM.render( element, document .getElementById('root' ) );
更新已渲染的元素
React
元素是 不可突变(immutable)
的. 一旦你创建了一个元素, 就不能再修改其子元素或任何属性
更新 UI 的唯一方法是:
创建一个新的元素, 并将其传入 ReactDOM.render()
方法.
大多数 React 应用只会调用 ReactDOM.render() 一次
,有状态的类组件会解决更新dom
问题
React DOM
会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新
, 以达到 DOM
所需的状态。
1 <div id ="root" > 时钟渲染更新元素:</div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function tick ( ) { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date ().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document .getElementById('root' ) ); } tick(); setInterval (tick, 1000 );
组件(Components) 和 属性(Props)
组件就像JavaScript
的函数,组件可以接收任意输入属性(称为”props
”)
Props , 即属性(Property),写法就像html标签的属性
组件名称必须以大写字母开头
: <div />
代表一个 DOM 标签,而 <Welcome />
则代表一个组件,并且需要在作用域中有一个 Welcome
组件
函数式组件,必须显式的传递props 1 2 3 4 5 6 7 8 9 10 11 function Welcome (props ) { return <h1 > Hello, {props.name}</h1 > ; } <Welcome name="aaa" /> <h1>Hello, aaa</h1>
类组件,不用显式的传递props
获取props
的时候前边必须加this
,this.props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Welcome extends React .Component { constructor (props){ super (props); } render() { return <h1 > Hello, {this.props.name}</h1 > ; } } <Welcome name="aaa" /> <h1>Hello, aaa</h1>
渲染一个组件
React
遇到一个代表用户定义组件的元素时,它将 JSX
中的属性以一个单独对象的形式传递给相应的组件。 我们将其称为 “props
” 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Welcome (props ) { return <h1 > Hello, {props.name}</h1 > ; } const element = <Welcome name ="Sara" /> ;ReactDOM.render( element, document .getElementById('root' ) ); ReactDOM.render( <Welcome name="Sara" />, document .getElementById('root' ) );
上面这个例子的渲染步骤:
我们调用了 ReactDOM.render()
方法并向其中传入了 <Welcome name="Sara" />
元素。
React 调用 Welcome 组件,并向其中传入了 {name: ‘Sara’} 作为 props 对象。
Welcome 组件返回 <h1>Hello, Sara</h1>
。
React DOM 迅速更新 DOM ,使其显示为 <h1>Hello, Sara</h1>
。
组件嵌套
组件可以在它们的输出中引用其它组件
提取组件的规则:会被复用的组件要提取出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Welcome (props ) { return <h1 > Hello, {props.name}</h1 > ; } function App ( ) { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); } ReactDOM.render( <App />, document .getElementById('root' ) );
Props 是只读的
无论用函数或类的方法来声明组件, 它都无法修改其自身 props
所有 React 组件都必须是纯函数,并禁止修改其自身 props
“纯函数” :不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果 1 2 3 function sum (a, b ) { return a + b; }
非纯函数:改变了自身的输入值 1 2 3 function withdraw (account, amount ) { account.total -= amount; }
状态(State) 和 生命周期 <a href="#a1">时钟渲染更新元素 </a>
组件提取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Clock (props ) { return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function tick ( ) { ReactDOM.render( <Clock date={new Date ()} />, document .getElementById('root' ) ); } setInterval (tick, 1000 );
理论上上边的例子:我们只需要引用一个 Clock , 不需要每次都调用ReactDOM.render函数它就可以自动计时并更新:
1 2 3 4 ReactDOM.render( <Clock />, document .getElementById('root' ) );
要实现这点,我们就需要用到 state
state
和 props
类似,但是state是私有的
,并且由组件本身完全控制。
类定义的组件
有一些额外的特性。 这个”类专有的特性”, 指的就是局部状态 state
。
把函数式组件转化为类组件
创建一个继承自 React.Component 类的 ES6 class 同名类。
添加一个名为 render() 的空方法。
把原函数中的所有内容移至 render() 中。
在 render() 方法中使用 this.props 替代 props
。
删除保留的空函数声明。
把Clock组件改写为类组件:
每次更新发生时(state发生变化时)都会调用 render
方法,只要我们将 <Clock />
渲染到同一个 DOM
节点中,就只会使用 Clock
类的单例(Clock 类的实例)这让我们可以使用额外功能,例如本地状态(state)
和 生命周期方法
。
1 2 3 4 5 6 7 8 9 10 class Clock extends React .Component { render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this .props.date.toLocaleTimeString()}.</h2> </div> ); } }
在类组件中添加本地状态(state)
初始化state必须在constructor中,并且调用constructor后必须使用super(props)将 props 传递给基础构造函数;
过以下3步, 把date从属性(props) 改为 状态(state):
替换 render() 方法中的 this.props.date 为 this.state.date:1 2 3 4 5 6 7 8 9 10 class Clock extends React .Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this .state.date.toLocaleTimeString()}.</h2> </div> ); } }
添加一个 类构造函数(constructor) 初始化 this.state:1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Clock extends React .Component { constructor (props) { super (props); this .state = {date : new Date ()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this .state.date.toLocaleTimeString()}.</h2> </div> ); } }
注意我们如何将 props 传递给基础构造函数(super(props)
):
类组件应始终使用 props 调用基础构造函数
1 2 3 4 constructor (props) { super (props); this .state = {date : new Date ()}; }
移除<Clock />
元素中的 date 属性:1 2 3 4 5 ReactDOM.render( <Clock />, document .getElementById('root' ) );
结果1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Clock extends React .Component { constructor (props) { super (props); this .state = {date : new Date ()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this .state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document .getElementById('root' ) );
在类组件中添加生命周期方法
当 Clock 第一次“挂载(mounting)” 时设置一个定时器。
当 Clock “卸载(unmounting)销毁”之前 ,清除定时器。
this.props 由 React 本身设定, 而 this.state 具有特殊的含义,如果需要存储不参与数据流的内容(如计时器id)
可以自由向该类(组件类本身不是state)中添加其他字段(不用声明变量,直接通过this.xxx
添加)
通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法
,获取了当前应该显示的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Clock extends React .Component { constructor (props){ super (props) this .state={date :new Date ()} } componentDidMount(){ this .timer=setInterval (()=> this .tick(),1000 ) } componentWillUnmount(){ clearInterval (this .timer) } tick(){ this .setState({ date:new Date () }) } render(){ return ( <div> <h1>Hello , 甘草倚半夏</h1> <h2>It is {this .state.date.toLocaleTimeString()}</h2> </div> ) } } ReactDOM.render( <Clock></Clock>, document .getElementById('root' ) )
不能直接修改 state(状态),使用this.setState() 改变state
唯一可以分配 (初始化state)this.state 的地方是构造函数construct
1 2 3 4 5 this .state.comment = 'Hello' ;this .setState({comment : 'Hello' });
state(状态) 更新可能是异步的,setState((state, props) => ({}))
React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新
因为this.props 和 this.state 可能是异步更新的,所以不能依赖他们的值计算下一个state(状态) 1 2 3 4 this .setState({ counter: this .state.counter + this .props.increment, });
解决办法:
利用setState接收一个函数的形式,setState((state, props) => ({}))
该函数接收前一个状态值(变化前的state)作为第 1 个参数, 并将更新后的props值作为第 2 个参数:
1 2 3 4 5 6 7 8 9 10 11 this .setState((state, props ) => ({ counter: state.counter + props.increment })); this .setState(function (state, props ) { return { counter: state.counter + props.increment }; });
state(状态)更新会被合并
合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换this.state.comments 的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 constructor (props) { super (props); this .state = { posts: [], comments: [] }; } componentDidMount() { fetchPosts().then(response => { this .setState({ posts: response.posts }); }); fetchComments().then(response => { this .setState({ comments: response.comments }); }); }
数据向下流动 (“单向”数据流)
state(状态) 被称为 本地状态 或 封装状态
的原因:无论作为父组件还是子组件,它都无法获悉另一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。
state不能被自身组件以外的任何组件访问
组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):
1 2 3 4 5 6 7 8 9 10 <h2>It is {this .state.date.toLocaleTimeString()}.</h2> <FormattedDate date={this .state.date} /> function FormattedDate (props ) { return <h2 > It is {props.date.toLocaleTimeString()}.</h2 > ; }
“单向”的数据流
任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件
。
所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动
所有组件都是完全独立的,独立更新
数据向下示例:
父级state
作为子级的props
传向子级
父级state
变化时子级不需要重新渲染就会跟着变化,因为:子组件存在于父组件的render中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class H2 extends React .Component { render(){ return ( <h2>It is {this .props.date}</h2> ) } } class Clock extends React .Component { constructor (props){ super (props) this .state={date :new Date ()} } componentDidMount(){ this .timer=setInterval (()=> this .tick(),1000 ) } componentWillUnmount(){ clearInterval (this .timer) } tick(){ this .setState({ date:new Date () }) } render(){ return ( <div> <h1>Hello , 甘草倚半夏</h1> <H2 date={this .state.date.toLocaleTimeString()}></H2> </div> ) } } ReactDOM.render( <Clock></Clock>, document .getElementById('root' ) )
处理事件
React 事件使用驼峰命名
,而不是全部小写。
通过 JSX
, 传递一个函数作为事件处理程序而不是一个字符串(onClick={}
而不是onClick=""
)。
在 React
中不能通过返回 false
(即 return false
; ) 来阻止默认行为。必须明确调用 e.preventDefault
React
中的e
是一个合成的事件事件对象,不需要担心跨浏览器的兼容性问题
不需要调用 addEventListener
在 DOM
元素被创建后添加事件监听器。只要当元素被初始渲染的时候提供一个监听器就可以了
使用ES6
类 定义一个组件时,一个事件处理程序就是类上的一个方法
绑定事件时onClick={this.youfunction}中的事件youfunction后面不能带()
不然会报错 ,但是加一层箭头函数就能带()
了onClick={()=>{this.youfunction()}}
函数组件中定义函数 必须是 : function xxx(){}
函数组件
获取事件
及props
不能带this
1 2 3 4 5 6 7 8 //原生HTML <button onclick ="activateLasers()" > Activate Lasers </button > //React中 <button onClick ={activateLasers} > Activate Lasers </button >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function ActionLink ( ) { function handleClick (e ) { e.preventDefault(); console .log('The link was clicked.' ); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
类组件添加事件示例
定义事件
在construct构造函数中绑定this,事件函数后面的.bind(this)
是必须的
在元素中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Toggle extends React .Component { constructor (props) { super (props); this .state = {isToggleOn : true }; this .handleClick = this .handleClick.bind(this ); } handleClick() { this .setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( <button onClick={this .handleClick}> {this .state.isToggleOn ? 'ON' : 'OFF' } </button> ); } } ReactDOM.render( <Toggle />, document .getElementById('root' ) );
在JSX
回调中必须注意 this
的指向,在 JavaScript
中,类方法默认是没有 绑定this
的。如果this.handleClick
未绑定this
并赋值给了onClick
事件,那么在this.handleClick
函数中this
会是 undefined
定义函数时使用 属性初始化语 ,可以解决this绑定问题
利用箭头函数定义事件:不使用属性初始化语,在回调中(事件绑定中)使用一个 箭头函数 ,也可以解决this绑定问题
,存在的问题是:每次 LoggingButton 渲染时都创建一个不同的回调,在这个回调被作为 prop(属性) 传递给下级组件,这些组件可能需要额外的重复渲染。
利用箭头函数定义事件,必须显式的传递事件对象e
如果函数需要传递给下级组件通常建议在构造函数中.bind(this)进行绑定
,以避免这类问题
属性初始化语法(就是声明式箭头函数) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class LoggingButton extends React .Component { handleClick = () => { console .log('this is:' , this ); } render() { return ( <button onClick={this .handleClick}> Click me </button> ); } } class LoggingButton extends React .Component { handleClick() { console .log('this is:' , this ); } render() { return ( <button onClick={(e ) => this .handleClick(e)}> Click me </button> ); } }
事件函数传参
在循环内部,通常需要将一个额外的参数
(比如id)传递给事件处理程序
参数 e
作为 React
事件对象将会被作为第二个参数进行传递,通过箭头函数的方式,事件对象必须显式的进行传递
普通事件(在constructor中.bind(this)的方式)传参 onClick={this.deleteRow.bind(this, id)}
箭头函数传参,显式的传递e并且e是第二个传递进函数的onClick={(e) => this.deleteRow(id, e)}
1 2 3 4 <button onClick ={(e) => this.deleteRow(id, e)}>Delete Row</button > <button onClick ={this.deleteRow.bind(this, id )}> Delete Row</button > //上述两行代码是等价的
在函数绑定不传参数
的时候,在函数体中默认第一个形参就是事件对象e
,不管定义形参名字是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class App extends React .Component { constructor (props){ super (props) this .clock=this .clock.bind(this ) } clock(data){ console .log(data.clientX); } render(){ return <h2 onClick ={this.clock} > 点我试试传参</h2 > } }
但是使用.bind(this,xxx)传递参数xxx时,形参就是传递的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class App extends React .Component { constructor (props){ super (props) this .clock=this .clock.bind(this ) } clock(data){ console .log(data); } render(){ return <h2 onClick ={this.clock.bind(this,15)} > 点我试试传参</h2 > } }
利用箭头函数进行参数传递时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class App extends React .Component { constructor (props){ super (props) this .clock=this .clock.bind(this ) } clock(data,e){ console .log(data); console .log(e) } render(){ return <h2 onClick ={(e) => {this.clock(15,e)}}>点我试试传参</h2 > } }
重写bind方法使传参时不必传递this 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Function .prototype.b = function (...args ) { var Fn = this ; return function (e ) { Fn(...args,e); } } click=(value )=> { dosomething... } onClick={this .click.b("1111" )}
合成事件与原生事件的区别
1、合成事件在jsx中直接绑定的事件,如
1 <a ref="aaa" onClick={(e )=> this .handleClick(e)}>更新</a>
这里的handleClick事件就是合成事件
2、原生事件通过js原生代码绑定的事件,如
1 2 3 4 5 6 7 8 9 10 11 document .body.addEventListener('click' ,e => { if (e.target&&e.target.matches('a' )){ return ; } console .log('body' ); }) this .refs.update.addEventListener('click' ,e => { console .log('update' ); });
事件冒泡处理
A、合成事件间,e.stopPropagation();
阻止合成事件间
的冒泡,在事件函数中添加e.stopPropagation();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import React,{ Component } from 'react' ;import ReactDOM,{findDOMNode} from 'react-dom' ;class Counter extends Component { constructor (props){ super (props); this .state = { count:0 , } } handleClick(e){ e.stopPropagation(); this .setState({count :++this .state.count}); } testClick(){ console .log('test' ) } render(){ return ( <div ref="test" onClick={()=> this .testClick()}> <p>{this .state.count}</p> <a ref="update" onClick={(e )=> this .handleClick(e)}>更新</a> </div> ) } } var div1 = document .getElementById('content' );ReactDOM.render(<Counter /> ,div1,()=> {});
阻止原生事件与最外层document上的事件间
的冒泡,用e.nativeEvent.stopImmediatePropagation();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import React,{ Component } from 'react' ;import ReactDOM,{findDOMNode} from 'react-dom' ;class Counter extends Component { constructor (props){ super (props); this .state = { count:0 , } } handleClick(e){ e.nativeEvent.stopImmediatePropagation(); this .setState({count :++this .state.count}); } render(){ return ( <div ref="test" > <p>{this .state.count}</p> <a ref="update" onClick={(e )=> this .handleClick(e)}>更新</a> </div> ) } componentDidMount() { document .addEventListener('click' , () => { console .log('document' ); }); } } var div1 = document .getElementById('content' );ReactDOM.render(<Counter /> ,div1,()=> {});
C、阻止合成事件与除最外层document上的原生事件,’判断e.target’
C、阻止合成事件与除最外层document上的原生事件
间的冒泡,通过判断e.target来避免
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import React,{ Component } from 'react' ;import ReactDOM,{findDOMNode} from 'react-dom' ;class Counter extends Component { constructor (props){ super (props); this .state = { count:0 , } } handleClick(e){ this .setState({count :++this .state.count}); } render(){ return ( <div ref="test" > <p>{this .state.count}</p> <a ref="update" onClick={(e )=> this .handleClick(e)}>更新</a> </div> ) } componentDidMount() { document .body.addEventListener('click' ,e => { if (e.target&&e.target.matches('a' )){ return ; } console .log('body' ); }) } } var div1 = document .getElementById('content' );ReactDOM.render(<Counter /> ,div1,()=> {});
条件渲染
React 中的条件渲染就和在 JavaScript 中的条件语句一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function UserGreeting (props ) { return <h1 > Welcome back!</h1 > ; } function GuestGreeting (props ) { return <h1 > Please sign up.</h1 > ; } function Greeting (props ) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting /> ; } return <GuestGreeting /> ; } ReactDOM.render( <Greeting isLoggedIn={false } />, document .getElementById('root' ) );
元素变量
用变量来存储元素,不同条件下将不同组件赋值给变量,然后直接用{变量}渲染变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 function LoginButton (props ) { return ( <button onClick={props.onClick}> Login </button> ); } function LogoutButton (props ) { return ( <button onClick={props.onClick}> Logout </button> ); } class LoginControl extends React .Component { constructor (props) { super (props); this .handleLoginClick = this .handleLoginClick.bind(this ); this .handleLogoutClick = this .handleLogoutClick.bind(this ); this .state = {isLoggedIn : false }; } handleLoginClick() { this .setState({isLoggedIn : true }); } handleLogoutClick() { this .setState({isLoggedIn : false }); } render() { const isLoggedIn = this .state.isLoggedIn; let button; if (isLoggedIn) { button = <LogoutButton onClick ={this.handleLogoutClick} /> ; } else { button = <LoginButton onClick ={this.handleLoginClick} /> ; } return ( <div> {button} </div> ); } } ReactDOM.render( <LoginControl />, document .getElementById('root' ) );
使用逻辑 && 操作符的内联 if 用法
运用的是逻辑与在赋值操作中的用法
如果条件为 true ,则 && 后面的元素将显示在输出中。 如果是 false,React 将会忽略并跳过它。
1、逻辑与在条件判断中的用法:
&&:所有条件都为真,整体才为真
||:只要有一个条件为真,整体都为真
2、在赋值操作中
||: A|| B 首先看A的真假,A为真返回的是A的值,A为假返回的B的值(不管B是啥) 1|| 2 => 1 ;0|| false=>false;
&& : A&& B 首先看A的真假,A为真返回B的值,A为假返回A的值;1&&2 => 2;0&&false => 0
真实项目中我们利用逻辑或
实现默认值的设置操作,形参的默认值赋值
callback && callback(); callback为真callback就执行
**逻辑与
的优先级高于 逻辑或
** 先计算 逻辑与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Mailbox (props ) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); } const messages = ['React' , 'Re: React' , 'Re:Re: React' ];ReactDOM.render( <Mailbox unreadMessages={messages} />, document .getElementById('root' ) );
内联三元运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 render() { const isLoggedIn = this .state.isLoggedIn; return ( <div> {isLoggedIn ? ( <LogoutButton onClick={this .handleLogoutClick} /> ) : ( <LoginButton onClick={this .handleLoginClick} /> )} </div> ); } render() { const isLoggedIn = this .state.isLoggedIn; return ( <div> The user is <b>{isLoggedIn ? 'currently' : 'not' }</b> logged in . </div> ); }
当条件变得太复杂时,可能是提取组件的好时机
防止组件渲染(返回null隐藏组件),react的显示隐藏 在下面的例子中,如果 prop 值为 false ,则该组件不渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function WarningBanner (props ) { if (!props.warn) { return null ; } return ( <div className="warning" > Warning! </div> ); } class Page extends React .Component { constructor (props) { super (props); this .state = {showWarning : true }; this .handleToggleClick = this .handleToggleClick.bind(this ); } handleToggleClick() { this .setState(state => ({ showWarning: !state.showWarning })); } render() { return ( <div> <WarningBanner warn={this .state.showWarning} /> <button onClick={this .handleToggleClick}> {this .state.showWarning ? 'Hide' : 'Show' } </button> </div> ); } } ReactDOM.render( <Page />, document .getElementById('root' ) );
从组件的 render 方法返回 null 不会影响组件生命周期方法的触发
列表(Lists) 和 键(Keys),arr.map((item,index)=>())
利用 arr.map((item,index)=>())
创建元素列表时,“key
” 是一个你需要包含的特殊字符串属性
,不然会报错
键(Keys
) 帮助 React
标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys
)来标识
key
最好使用一个在它的同辈元素中不重复的标识字符串
。可以使用数据中的 ID 作为 keys,没有ID 时,可以用数据项的索引值作为 key ,如果列表项的顺序可能发生改变,不建议使用索引作为 keys
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const numbers = [1 , 2 , 3 , 4 , 5 ];const listItems = numbers.map((number ) => <li key={number.toString()}>{number}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document .getElementById('root' ) ); const todoItems = todos.map((todo, index ) => <li key={index}> {todo.text} </li> );
使用 keys 提取组件
key 应该放置在有数组处理的map函数中(<ListItem />)
的元素中,不能放在 ListItem 组件自身中的 <li>
根元素上
元素中只要调用 map() 就需要绑定 keys
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function ListItem (props ) { return <li > {props.value}</li > ; } function NumberList (props ) { const numbers = props.numbers; const listItems = numbers.map((number ) => <ListItem key={number.toString()} value={number} /> ); return ( <ul> {listItems} </ul> ); } const numbers = [1 , 2 , 3 , 4 , 5 ];ReactDOM.render( <NumberList numbers={numbers} />, document .getElementById('root' ) );
在数组中使用的 keys 必须在它们的同辈之间唯一。然而并不需要全局唯一。可以在操作两个不同数组的时候使用相同的 keys
键(keys)是React的一个内部映射,不会传递给组件的内部(组件内部获取不到keys)
。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。
1 2 3 4 5 6 const content = posts.map((post ) => <Post key={post.id} id={post.id} title={post.title} /> );
上面的例子中, Post 组件可以读取 props.id,但是不能读取 props.key 。
在 JSX 中嵌入 map()
JSX允许在大括号中嵌入任何表达式,因此可以 内联 map()
1 2 3 4 5 6 7 8 9 10 function NumberList (props ) { const numbers = props.numbers; return ( <ul> {numbers.map((number ) => <ListItem key={number.toString()} value={number} /> )} </ul> ); }
如果 map() 体中有太多嵌套,是提取组件的好时机。
受控组件(Controlled Components)
在 HTML 中,表单元素如 <input>,<textarea> 和 <select>
表单元素通常保持自己的状态,并根据用户输入进行更新
。
而在 React 中,可变状态一般保存在组件的 state(状态) 属性中
,并且只能通过 setState() 更新
。
“受控组件”
:通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为
。这种形式,其值由 React 控制的输入表单元素
称为“受控组件”。
受控组件示例
设置表单元素的 value
属性之后,其显示值将由 this.state.value
决定,以满足 React
状态的同一数据理念。每次键盘敲击之后会执行 handleChange
方法以更新 React
状态,显示值也将随着用户的输入改变。
event.target
获取触发事件的元素
event.preventDefault()
阻止默认事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class NameForm extends React .Component { constructor (props) { super (props); this .state = {value : '' }; this .handleChange = this .handleChange.bind(this ); this .handleSubmit = this .handleSubmit.bind(this ); } handleChange(event) { this .setState({value : event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this .state.value); event.preventDefault(); } render() { return ( <form onSubmit={this .handleSubmit}> <label> Name: <input type="text" value={this .state.value} onChange={this .handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
对于受控组件来说,每一次 state
(状态) 变化都会伴有相关联的处理函数。这使得可以直接修改或验证用户的输入
。
比如,如果我们希望强制 name
的输入都是大写字母,可以这样来写 handleChange
方法:
1 2 3 handleChange(event) { this .setState({value : event.target.value.toUpperCase()}); }
页面显示数据步骤: state.value -> 用户输入->处理函数 ->state.value
textare 标签
在 HTML 中,<textarea>
元素通过它的子节点定义了它的文本值:
在 React 中,<textarea>
的赋值使用 value
属性替代。这样一来,表单中 <textarea> 的书写方式与单行文本输入框一样
注意,this.state.value
在构造函数中初始化,所以这些文本一开始就出现在文本域中。
1 2 3 4 5 <textarea> Hello there, this is some text in a text area </textarea>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class EssayForm extends React .Component { constructor (props) { super (props); this .state = { value: 'Please write an essay about your favorite DOM element.' }; this .handleChange = this .handleChange.bind(this ); this .handleSubmit = this .handleSubmit.bind(this ); } handleChange(event) { this .setState({value : event.target.value}); } handleSubmit(event) { alert('An essay was submitted: ' + this .state.value); event.preventDefault(); } render() { return ( <form onSubmit={this .handleSubmit}> <label> Essay: <textarea value={this .state.value} onChange={this .handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
select 标签: selected
在 HTML 中,<select>
创建了一个下拉列表,通过给 option
设置 selected
属性设置默认选中;
React
中,并不使用这个 selected
属性,而是在根 select
标签中使用了一个 value
属性
可以将一个数组
传递给 value
属性 并开启multiple={true}
属性,允许你在 select
标签中选择多个选项
1 2 3 4 5 6 7 <select > <option value ="grapefruit" > Grapefruit</option > <option value ="lime" > Lime</option > <option selected value ="coconut" > Coconut</option > <option value ="mango" > Mango</option > </select >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class FlavorForm extends React .Component { constructor (props) { super (props); this .state = {value : 'coconut' }; this .handleChange = this .handleChange.bind(this ); this .handleSubmit = this .handleSubmit.bind(this ); } handleChange(event) { this .setState({value : event.target.value}); } handleSubmit(event) { alert('Your favorite flavor is: ' + this .state.value); event.preventDefault(); } render() { return ( <form onSubmit={this .handleSubmit}> <label> Pick your favorite flavor: <select value={this .state.value} onChange={this .handleChange}> <option value="grapefruit" >Grapefruit</option> <option value="lime" >Lime</option> <option value="coconut" >Coconut</option> <option value="mango" >Mango</option> </select> </label> <input type="submit" value="Submit" /> </form> ); } } <select multiple={true } value={['B' , 'C' ]}>
处理多个输入元素
处理多个受控的 input
元素时,可以为每个元素添加一个 name
属性,并且让处理函数根据 event.target.name
的值来决定要做什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Reservation extends React .Component { constructor (props) { super (props); this .state = { isGoing: true , numberOfGuests: 2 }; this .handleInputChange = this .handleInputChange.bind(this ); } handleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; this .setState({ [name]: value }); } render() { return ( <form> <label> Is going: <input name="isGoing" type="checkbox" checked={this .state.isGoing} onChange={this .handleInputChange} /> </label> <br /> <label> Number of guests: <input name="numberOfGuests" type="number" value={this .state.numberOfGuests} onChange={this .handleInputChange} /> </label> </form> ); } }
在 受控组件 上指定值 prop
可防止用户更改输入,除非您希望如此。 如果你已经指定了一个 value
,但是输入仍然是可编辑的,你可能会意外地把 value
设置为undefined
或 null
。 以下代码演示了这一点。 (输入首先被锁定,但在短暂的延迟后可以编辑。)
1 2 3 4 5 6 ReactDOM.render(<input value ="hi" /> , mountNode); setTimeout (function ( ) { ReactDOM.render(<input value ={null} /> , mountNode); }, 1000 );
Formik
是完整的解决方案,包括验证、跟踪访问的字段以及处理表单提交
不受控组件
推荐使用受控组件来实现表单
在受控组件中,表单数据由 React
组件负责处理。
不受控组件,其表单数据由 DOM
元素本身处理。
不受控组件:使用一个 ref
来从 DOM
获得 表单值,而不是为每个状态更新编写一个事件处理程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class NameForm extends React .Component { constructor (props) { super (props); this .handleSubmit = this .handleSubmit.bind(this ); this .input = React.createRef(); } handleSubmit(event) { alert('A name was submitted: ' + this .input.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this .handleSubmit}> <label> Name: <input type="text" ref={this .input} /> </label> <input type="submit" value="Submit" /> </form> ); } }
不受控组设置默认值:使用defaultValue
不是 value
希望 不受控组 有初始值,并且后续更新不受控制,应使用defaultValue
属性而不是 value
属性
<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked
,而 <select> 和 <textarea> 支持 defaultValue
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 render() { return ( <form onSubmit={this .handleSubmit}> <label> Name: <input defaultValue="Bob" type="text" ref={(input ) => this .input = input} /> </label> <input type="submit" value="Submit" /> </form> ); }
在HTML中, <input type="file">
可以让用户从设备存储器中选择一个或多个文件上传到服务器
因为它的值是只读的,所以它是 React
中的 不受控 组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class FileInput extends React .Component { constructor (props) { super (props); this .handleSubmit = this .handleSubmit.bind(this ); this .fileInput = React.createRef(); } handleSubmit(event) { event.preventDefault(); alert( `Selected file - ${ this .fileInput.current.files[0 ].name } ` ); } render() { return ( <form onSubmit={this .handleSubmit}> <label> Upload file: <input type="file" ref={this .fileInput} /> </label> <br /> <button type="submit" >Submit</button> </form> ); } } ReactDOM.render( <FileInput />, document .getElementById('root' ) );
状态提升(Lifting State Up)
同一个数据的变化需要几个不同的组件来反映
时,需要提升这个共享的状态到用到它的组件的最近的祖先组件中,(依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态)
在一个 React
应用中,对于任何可变的数据都应该遵循“单一数据源”原则
。
当一个数据可以从 props
(属性) 或者 state
(状态) 得到,那么不应该从 state
(状态) 中获取,而是从props
获取(能从props
中获取的数据,就不要用state
)
状态提升,最主要的在于子组件接收父组件的数据、子组件怎么调用父组件的函数来改变父组件的数据,明白这一点以后,就是组件的嵌套
温度计算器示例
当 temperature
是TemperatureInput
组件的 本地 state
(状态)时, TemperatureInput
可以调用 this.setState()
来更改它。 然而,当 temperature 是父级的state作为 prop(属性)传递进来的 ,TemperatureInput 就无法控制它
·
要更新其温度时,它就会调用this.props.onTemperatureChange
代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const scaleNames = { c: '摄氏温度' , f: '华氏温度' }; function toCelsius (fahrenheit ) { return (fahrenheit - 32 ) * 5 / 9 ; } function toFahrenheit (celsius ) { return (celsius * 9 / 5 ) + 32 ; } function tryConvert (temperature, convert ) { const input = parseFloat (temperature); if (Number .isNaN(input)) { return '' ; } const output = convert(input); const rounded = Math .round(output * 1000 ) / 1000 ; return rounded.toString(); } function BoilingVerdict (props ) { if (props.celsius >= 100 ) { return <p > 水沸腾了!</p > ; } return <p > 水没有沸腾!</p > ; } class TemperatureInput extends React .Component { constructor (props) { super (props); this .handleChange = this .handleChange.bind(this ); } handleChange(e) { this .props.onTemperatureChange(e.target.value); } render() { const temperature = this .props.temperature; const scale = this .props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this .handleChange} /> </fieldset> ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Calculator extends React .Component { constructor (props) { super (props); this .handleCelsiusChange = this .handleCelsiusChange.bind(this ); this .handleFahrenheitChange = this .handleFahrenheitChange.bind(this ); this .state = {temperature : '' , scale : 'c' }; } handleCelsiusChange(temperature) { this .setState({scale : 'c' , temperature :temperature}); } handleFahrenheitChange(temperature) { this .setState({scale : 'f' , temperature :temperature}); } render() { const scale = this .state.scale; const temperature = this .state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return ( <div> <TemperatureInput scale="c" temperature={celsius} onTemperatureChange={this .handleCelsiusChange} /> <TemperatureInput scale="f" temperature={fahrenheit} onTemperatureChange={this .handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat (celsius)} /> </div> ); } }
数据流程
输入内容改变 >> 触发change调用父级change将输入内容传入父级 >> 父级数据改变 >> 传入子组件 >> 子组件显示父组件传过来的数据
子组件调用父组件的函数
将父级的函数通过props
传递给子级,子级通过this.props.xxx
就可以调用父级的函数并对父级的数据进行更改
组合(Composition) VS 继承(Inheritance)
建议使用组合
而不是继承
来实现代码的重用。
包含(children prop)
如果组件在设计前无法获知自己要使用什么子组件,那么使用特别的 children prop
来直接传递 子元素
到他们的输出中(组件内使用{props.children}
接收组件使用时在标签中传入的子级元素)
组件JSX
标签中的任何内容(包括直接写的文字)都会被传递到组件中,作为一个 children prop
(属性)通过{props.children}
获取
组件JSX 标签中的内容,在组件中不使用{props.children}
接收时,是不显示
的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function FancyBorder (props ) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } function WelcomeDialog ( ) { return ( <FancyBorder color="blue" > <h1 className="Dialog-title" > Welcome </h1> <p className="Dialog-message" > Thank you for visiting our spacecraft! </p> </FancyBorder> ); }
通过“占位符”,接收多组子元素
在一个组件中可能需要多个 “占位符” ,这种情况下使用自定义的 prop
(属性),而不是使用 props.children
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function SplitPane (props ) { return ( <div className="SplitPane" > <div className="SplitPane-left" > {props.left} </div> <div className="SplitPane-right" > {props.right} </div> </div> ); } function App ( ) { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
left
和right
既可以是React元素
也可以是HTML标签元素
特例(通过组合实现组件的可配置,props和props.children 组合)
使用组合来实现,一个偏“特殊”的组件渲染出一个偏“通用”的组件,通过 props
(属性) 配置它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Dialog (props ) { return ( <div color="blue" > <h1 className="Dialog-title" > {props.title} </h1> <p className="Dialog-message" > {props.message} </p> </div> ); } function WelcomeDialog ( ) { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 function Dialog (props ) { return ( <div color="blue" > <h1 className="Dialog-title" > {props.title} </h1> <p className="Dialog-message" > {props.message} </p> {props.children} </div> ); } class SignUpDialog extends React .Component { constructor (props) { super (props); this .handleChange = this .handleChange.bind(this ); this .handleSignUp = this .handleSignUp.bind(this ); this .state = {login : '' }; } handleChange(e) { this .setState({login : e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this .state.login} !` ); } render() { return ( <Dialog title="Mars Exploration Program" message="How should we refer to you?" > <input value={this .state.login} onChange={this .handleChange} /> <button onClick={this .handleSignUp}> Sign Me Up! </button> </Dialog> ); } }
通过组合和继承实现对话框的一种方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 function Dialog (props ) { if (!props.show){ return null ; } return ( <div color="blue" > <h1 className="Dialog-title" > {props.title} </h1> <p className="Dialog-message" > {props.message} </p> {props.children} </div> ); } class SignUpDialog extends React .Component { constructor (props){ super (props); this .state={show :false } this .change=this .change.bind(this ) } change(e){ console .log(e.target.name); const show=eval (e.target.name); this .setState({show :show}) } render(){ return ( <div> <Dialog title="添加弹层" message="add" show={this .state.show}> <button name="false" onClick={this .change}>关闭</button> </Dialog> <button name="true" onClick={this .change}>添加</button> </div> ) } } ReactDOM.render( <SignUpDialog></SignUpDialog>, document .getElementById('root' ) );
使用 props(属性) 和 组合已经足够灵活来明确、安全的定制一个组件的外观和行为,不推荐用继承层次结构来创建组件。切记,组件可以接受任意的 props(属性) ,包括原始值、React 元素,或者函数。
React 的编程思想 步骤1:将 UI 拆解到组件层次结构中 步骤2: 用 React 构建一个静态版本 步骤3: 确定 UI state(状态) 的最小但能完整展现功能的父级组件 步骤4:确定哪个组件可变,或者说哪个组件使用这些 state(状态) 。 步骤5:添加反向数据流(就是:子组件改变父组件数据,一般通过props将改变父组件数据的父组件函数传递给子组件)
可访问性
在 JSX
中,label
标签的 for
特性被写作htmlFor
:
1 2 <label htmlFor="namedInput" >Name:</label> <input id="namedInput" type="text" name="name" />
片段(fragments)(类似 小程序 的 block标签、vue的 template)
片段(fragments
) 可以将子元素列表添加到一个分组中
,并且不会在DOM 中增加额外节点
。
有时候向 JSX
中添加 <div>
元素,会破坏 React
代码正常工作,特别是在使用列表(<ol>,<ul>和<dl>
)以及 HTML <table>
。在这些情况下,应该使用 React
片段(Fragments
) 将多个元素组合在一起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Columns extends React .Component { render() { return ( <div> <td>Hello</td> <td>World</td> </div> ); } } class Table extends React .Component { render() { return ( <table> <tr> <Columns /> </tr> </table> ); } } <table> <tr> <div> <td>Hello</td> <td>World</td> </div> </tr> </table>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Columns extends React .Component { render() { return ( <React.Fragment> <td>Hello</td> <td>World</td> </React.Fragment> ); } } import React, { Fragment } from 'react' ;class Columns extends React .Component { render() { return ( <Fragment> <td>Hello</td> <td>World</td> </Fragment> ); } } <table> <tr> <td>Hello</td> <td>World</td> </tr> </table>
简写语法<></>
<></>
,不支持 键(keys
) 或 属性(attributes
)
1 2 3 4 5 6 7 8 9 10 class Columns extends React .Component { render() { return ( <> <td>Hello</td> <td>World</td> </> ); } }
绑定kay及属性必须用<Fragment></Fragment>
1 2 3 4 5 6 7 8 9 10 11 12 13 function Glossary (props ) { return ( <dl> {props.items.map(item => ( <Fragment key={item.id}> <dt>{item.term}</dt> <dd>{item.description}</dd> </Fragment> ))} </dl> ); }
插槽(Portal)
将子节点渲染到父组件 DOM
层次结构之外的 DOM
节点(将子节点渲染到 React tree 的任何地方,不限于父节点)
1 ReactDOM.createPortal(child, container)
第一个参数(child
)是任何可渲染的 React 子元素
(一个元素,字符串或 片段(fragment))
第二个参数(container
)则是一个 DOM 元素
通常来说,当从组件的 render
方法返回一个元素时,它将被作为子元素被装载到最近父节点 DOM
中:
1 2 3 4 5 6 7 8 render() { return ( <div> {this .props.children} </div> ); }
然而,有时候希望将子元素插入到 DOM
节点的其他位置:
React 不会
创建一个新的 div
。
把 children
渲染到 domNode
中
domNode
可以是任何有效的 DOM 节点,不管它在 DOM 中的位置
1 2 3 4 5 6 7 8 render() { return ReactDOM.createPortal( this .props.children, domNode, ); }
典型用例: 当父组件有 overflow: hidden
或 z-index
样式,但你需要子组件能够在视觉上 “跳出(break out)” 其容器
(子组件不被父组件的overflow: hidden和z-index 影响
)。例如,对话框、hovercards
以及提示框:
1 2 <div id ="app-root" > </div > <div id ="modal-root" > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 const appRoot=document .getElementById('app-root' );const modalRoot=document .getElementById('modal-root' );class Modal extends React .Component { constructor (props){ super (props); this .el=document .createElement('div' ) } componentDidMount(){ modalRoot.appendChild(this .el) } componentWillUnmount(){ modalRoot.removeChild(this .el) } render(){ return ReactDOM.createPortal( this .props.children, this .el ) } } class App extends React .Component { constructor (props){ super (props) this .state={ showModal:false } this .handleShow = this .handleShow.bind(this ); this .handleHide = this .handleHide.bind(this ); } handleShow() { this .setState({showModal : true }); } handleHide() { this .setState({showModal : false }); } render(){ const modal=this .state.showModal?( <Modal> <div className="modal" > <div> 通过portal, 我们可以将内容呈现为 是DOM的一部分,就好像它是任何其他react子对象一样. </div> 这是在#modal-container div中呈现的 <button onClick={this .handleHide}>Hide modal</button> </div> </Modal> ):null ; return ( <div className="app" > 这个div 是 overflow: hidden. <button onClick={this .handleShow}>Show modal</button> {modal} </div> ) } } ReactDOM.render( <App></App>, appRoot )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #modal-root { position : relative; z-index : 999 ; } .app { height : 10em ; width : 10em ; background : lightblue; overflow : hidden; } .modal { background-color : rgba (0 ,0 ,0 ,0.5 ); position : fixed; height : 100% ; width : 100% ; top : 0 ; left : 0 ; display : flex; align-items : center; justify-content : center; }
Portals 存在事件冒泡
虽然portal
可以被放置在 DOM 树
的任何地方,但在其他方面其行为和普通的 React
子节点行为一致
。
如上下文特性(Context
)依然能够作用在portal
生成的子节点上
由于 portal
仍存在于 React tree
中,所以portal
依然存在事件冒泡, portal
内部会触发的事件会一直冒泡至包含 React tree
的祖先
严格模式(Strict Mode)为其后代元素触发额外的检查和警告
StrictMode
是一个用以标记出应用中潜在问题的工具。就像 Fragment
,StrictMode
不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告
。
可以在应用的任何地方启用严格模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React from 'react' ;function ExampleApplication ( ) { return ( <div> <Header /> <React.StrictMode> <div> <ComponentOne /> <ComponentTwo /> </div> </React.StrictMode> <Footer /> </div> ); }
使用 PropTypes 进行类型检查
使用 prop-types
库 来定义contextTypes
propTypes
仅在开发模式
中检测
1 2 3 4 5 6 7 8 9 10 11 12 13 import PropTypes from 'prop-types' ;class Greeting extends React .Component { render() { return ( <h1>Hello, {this .props.name}</h1> ); } } Greeting.propTypes = { name: PropTypes.string };
验证器列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import PropTypes from 'prop-types' ;MyComponent.propTypes = { optionalArray: PropTypes.array, optionalBool: PropTypes.bool, optionalFunc: PropTypes.func, optionalNumber: PropTypes.number, optionalObject: PropTypes.object, optionalString: PropTypes.string, optionalSymbol: PropTypes.symbol, optionalNode: PropTypes.node, optionalElement: PropTypes.element, optionalMessage: PropTypes.instanceOf(Message), optionalEnum: PropTypes.oneOf(['News' , 'Photos' ]), optionalUnion: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, PropTypes.instanceOf(Message) ]), optionalArrayOf: PropTypes.arrayOf(PropTypes.number), optionalObjectOf: PropTypes.objectOf(PropTypes.number), optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number }), requiredFunc: PropTypes.func.isRequired, requiredAny: PropTypes.any.isRequired, customProp: function (props, propName, componentName ) { if (!/matchme/ .test(props[propName])) { return new Error ( 'Invalid prop `' + propName + '` supplied to' + ' `' + componentName + '`. Validation failed.' ); } }, customArrayProp: PropTypes.arrayOf(function (propValue, key, componentName, location, propFullName ) { if (!/matchme/ .test(propValue[key])) { return new Error ( 'Invalid prop `' + propFullName + '` supplied to' + ' `' + componentName + '`. Validation failed.' ); } }) };
要求单独的 Child
使用 PropTypes.element
,可以指定仅将单一子元素传递给组件,作为子节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import PropTypes from 'prop-types' ;class MyComponent extends React .Component { render() { const children = this .props.children; return ( <div> {children} </div> ); } } MyComponent.propTypes = { children: PropTypes.element.isRequired };
给prop设置默认值:defaultProps
用defaultProps
属性为 props
定义默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Greeting extends React .Component { render() { return ( <h1>Hello, {this .props.name}</h1> ); } } Greeting.defaultProps = { name: 'Stranger' }; ReactDOM.render( <Greeting />, document .getElementById('example' ) );
声明 defaultProps 作为静态属性
如果父组件没有为 this.props.name
传值,defaultProps
会给其一个默认值。
propTypes
的类型检测是在defaultProps
解析之后发生的,因此也会对默认属性 defaultProps
进行类型检测
1 2 3 4 5 6 7 8 9 10 11 class Greeting extends React .Component { static defaultProps = { name: 'stranger' } render() { return ( <div>Hello, {this .props.name}</div> ) } }
dangerouslySetInnerHTML 及 style dangerouslySetInnerHTML:代替在浏览器 DOM 中使用 innerHTML
是 React
用于代替在浏览器 DOM
中使用 innerHTML
从代码设置HTML
是有风险的,因为很容易被 跨站点脚本(XSS)
攻击
React
设置 HTML
,必须使用dangerouslySetInnerHTML
并通过 __html
键传递一个对象
1 2 3 4 5 6 7 function createMarkup ( ) { return {__html : 'First · Second' }; } function MyComponent ( ) { return <div dangerouslySetInnerHTML ={createMarkup()} /> ; }
style
一般不建议在元素上应用 style 属性
渲染时使用 style
来添加动态计算的样式
与 JavaScript DOM
的 style
属性一致,但是更高效,并且防止XSS
安全漏洞
设置和获取都是用 驼峰命名
style
接受一个对象,这个对象里面是这个元素的 CSS
属性键值对,原来 CSS
属性中带 - 的元素
都必须要去掉 - 换成驼峰命名
,如 font-size
换成 fontSize
,text-align
换成 textAlign
。
1 2 3 4 5 6 7 8 const divStyle = { color: 'blue' , backgroundImage: 'url(' + imgUrl + ')' , }; function HelloWorldComponent ( ) { return <div style ={divStyle} > Hello World!</div > ; }
样式不自动进行兼容
。 要支持旧版本的浏览器,需要提供相应的样式属性
1 2 3 4 5 6 7 8 const divStyle = { WebkitTransition: 'all' , msTransition: 'all' }; function ComponentWithTransition ( ) { return <div style ={divStyle} > This should work cross-browser</div > ; }
React 会自动为某些内联样式的数字属性值附加一个 “px” 后缀
,不想使用px时应该明确单位
1 2 3 4 5 6 7 8 9 <div style={{ height : 10 }}> Hello World! </div> <div style={{ height : '10%' }}> Hello World! </div>