props
- 在函数式组件里面, 使用传参的形式拿到props
- 在es6 class语法里面, 使用this.props拿到 props
- props是只读属性
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ES6 class写法
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
生命周期
componentDidMount 组件挂载完毕
componentWillUnmount 组件即将卸载
state
constructor(props) {
super(props);
this.state = {date: new Date()}; // 定义一个state
}
- 注意事项:
this.state.comment = 'Hello'; // 此代码不会重新渲染组件:
|
| 正确使用方式
| --------------->
this.setState({comment: 'Hello'});
- 注意事项2
this.props为异步获取数据, this.state也可能是异步获取数据, 当props或state数据改变, 可能会导致数据不会更新
this.setState({
counter: this.state.counter + this.props.increment,
});
|
| 正确使用方式
| --------------->
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
|
| 或正确使用方式
| --------------->
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
事件绑定
- 基础例子:
<button onClick={activateLasers}>
Activate Lasers
</button>
- 注意事项:
- 不能 return false的方式 阻止默认行为 必须明确的使用 preventDefault
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
- 绑定this
JSX 回调函数中的 this,类的方法默认是不会绑定 this 的, this 的值会是 undefined。
解决方法1: 在constructor 中显示的为 函数 使用 Bind方法绑定this
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this); // 这里绑定了this
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
解决方法2: 函数使用es6箭头函数的方式声明
handleClick = () => {
console.log('this is:', this);
}
解决方法3:
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
解决方法4:
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
条件渲染
1.在方法内部使用if/else
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
- 在render内部使用判断语句
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <div>我是组件1</div;
} else {
button =<div>我是组件2</div;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
- 使用与运算符 &&
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
- 三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
|
|
| 或这样
------------->
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
- 阻止组件渲染
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
map渲染
循环的时候记得加key
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
keys 与组件
- 被循环的根组件内添加key
- 数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的
return (
// 错啦!你不需要在这里指定key:
<li key={value.toString()}>
{value}
</li>
);
const listItems = numbers.map((number) =>
//错啦!元素的key应该在这里指定:
<ListItem value={number} />
);
|
|
| 正确使用
|-------》
function ListItem(props) {
// 对啦!这里不需要指定key:
return <li>{props.value}</li>;
}
const listItems = numbers.map((number) =>
// 又对啦!key应该在数组的上下文中被指定
<ListItem key={number.toString()}
value={number} />
);
JSX允许在大括号中嵌入任何表达式,
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
受控组件
- input
- 使用value={this.state.value}绑定数据, 然后通过change事件修改state.value数据
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} />
);
}
}
- textarea
textarea标签的使用方式跟Input一样, 不同的是, 在react中, textarea会用value属性来代替
- select
Coconut选项最初由于selected属性是被选中的。在React中,并不使用之前的selected属性,而在根select标签上用value属性来表示选中项
<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>
- input file标签
由于该标签的 value 属性是只读的, 所以它是 React 中的一个非受控组件。
组件交互(状态提升)
//------------------------ 父组件
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.state = {temperature: ''};
}
// 注册方法
handleCelsiusChange(temperature) {
this.setState({temperature});
}
render() {
const celsius = this.state.temperature;
return (
<div>
<TemperatureInput
// 传递参数值给子组件
temperature={celsius}
// 将方法通过prop传递过去
onTemperatureChange={this.handleCelsiusChange} />
</div>
);
}
}
//------------------------ 子组件
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;
return (
<fieldset>
// 接收到父组件传递过来的参数, 并赋值给自己
<input value={temperature}
// 注册自己的change事件
onChange={this.handleChange} />
</fieldset>
);
}
}
props.children 子代
通过props.children渲染组件内的内容
- 渲染内部所有内容
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>
);
}
- 内部内容指定渲染位置
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 />
} />
);
}
jsx与react
- react组件与react调用
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
|
| 转换
|----->
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me' // 当使用自闭合标签(<div/>) 的时候, 里面是没有内容的,可以使用Null放在这里
)
- 点表示法
也可以使用对象的形式作为组件
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" />;
}
- 首字母大写
import React from 'react';
// 正确!组件名应该首字母大写:
function Hello(props) {
// 正确!div 是有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正确!React 能够将大写开头的标签名认为是 React 组件。
return <Hello toWhat="World" />;
}
- 组件变量
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// 正确!JSX 标签名可以为大写开头的变量。
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
//-------------------------------
// 错误!JSX 标签名不能为一个表达式。
return <components[props.storyType] story={props.story} />;
}
- 属性中的表达式
你可以传递任何 {} 包裹的 JavaScript 表达式作为一个属性值
<MyComponent foo={1 + 2 + 3 + 4} />
if 语句和 for 循环在 JavaScript 中不是表达式,因此它们不能直接在 JSX 中使用,但是你可以将它们放在周围的代码中。
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>;
}
- 字符串常量
<MyComponent message="hello world" />
=========
<MyComponent message={'hello world'} />
- 默认为 True
<MyTextBox autocomplete />
=======
<MyTextBox autocomplete={true} />
- 扩展属性
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
- 渲染时, 布尔值, Null, undefined 被忽略, 下面的都等价
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
propTypes检测类型
- 限制类型
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
具体验证例子请打开该链接
https://react.docschina.org/docs/typechecking-with-proptypes.html
- 限制单个子代
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
// This must be exactly one element or it will warn.
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
- 属性默认值
// 为属性指定默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
refs
- 你不能在函数式组件上使用 ref 属性
- ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前
使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
- 通过this.myRef.current 拿到dom元素
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建 ref 存储 textInput DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:通过 "current" 取得 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
- 回调 Refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 这里的回调 把当前元素赋值给this.textInput
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 直接使用原生 API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 渲染后文本框自动获得焦点
this.focusTextInput();
}
render() {
// 使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
非受控组件
- 基础用法
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
// 这里拿到DOM元素
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
- 默认值 defaultValue
你希望 React 可以为其指定初始值,但不再控制后续更新。 你可以指定一个 defaultValue 属性而不是 value
同样,<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked,<select> 和 <textarea> 支持 defaultValue.
<input
defaultValue="Bob"
type="text"
ref={(input) => this.input = input} />
Context
Context 可以用来跨子组件给更下级的组件传参
- 创建一个Context
- Provider 作为传递组件的根组件
- Consumer 作为Provider 下的接收参数的组件Consumer
const {Provider, Consumer} = React.createContext(defaultValue);
- 接收一个 value 属性传递给 Provider 的后代 Consumers。一个 Provider 可以联系到多个 Consumers。
<Provider value={/* some value */}>
- Consumer
<Consumer>
// 这里接收一个函数, 传递值为 Provider的value
{value => /* render something based on the context value */}
</Consumer>
示例
export const themes = {
light: {
foreground: '#ffffff',
background: '#222222',
},
dark: {
foreground: '#000000',
background: '#eeeeee',
},
};
export const ThemeContext = React.createContext(
themes.dark // 默认值
);
import {ThemeContext} from './theme-context';
function ThemedButton(props) {
return (
// 定义 Consumer
<ThemeContext.Consumer>
{theme => (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
)}
</ThemeContext.Consumer>
);
}
export default ThemedButton;
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
render() {
return (
<Page>
// 定义 Provider
<ThemeContext.Provider value={this.state.theme}>
<ThemedButton onClick={this.toggleTheme} /> // 点击切换Provider.value
</ThemeContext.Provider>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
Fragments 空的 JSX 标签:
- 作用: 当渲染td的时候, 需要一个根组件, 但却不能随便使用, 因此可以使用一个空标签代替;
class Columns extends React.Component {
render() {
return (
<div>
<td>Hello</td>
<td>World</td>
</div>
);
}
}
|
|
| 输出结果
|- -------->
<table>
<tr>
<div> // 这里多了div
<td>Hello</td>
<td>World</td>
</div>
</tr>
</table>
- <React.Fragment> 与<>
<React.Fragment> == <>
- Fragment的属性
- key 是唯一可以传递给 Fragment 的属性
<React.Fragment key={item.id}></React.Fragment>