React 核心概念
React Key Concept
- React Key Concept
Hello World
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);
Rendering Elements
Rendering an Element into the DOM
<div id="root"></div>
We call this a “root” DOM node because everything inside it will be managed by React DOM.
Applications built with just React usually have a single root DOM node. If you are integrating React into an existing app, you may have as many isolated root DOM nodes as you like.
To render a React element, first pass the DOM element to ReactDOM.createRoot(), then pass the React element to root.render():
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
Updating the Rendered Element
React elements are immutable. Once you create an element, you can’t change its children or attributes. An element is like a single frame in a movie: it represents the UI at a certain point in time.
With our knowledge so far, the only way to update the UI is to create a new element, and pass it to root.render().
Consider this ticking clock example:
const root = ReactDOM.createRoot(
document.getElementById('root')
);
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);
}
setInterval(tick, 1000);
// It calls root.render() every second from a setInterval() callback.
Components and Props
Components are one of the core concepts of React
- a React component is a JavaScript function that you can sprinkle with markup.
- React lets you create components, reusable UI elements for your app.
- In a React app, every piece of UI is a component.
Props are Read-Only
- Pure Functions: do not attempt to change their inputs, and always return the same result for the same inputs
- All React components must act like pure functions with respect to their props.
Build Your Own Components
- Export the component
- Define the function
- Add markup
React components are regular JavaScript functions, but their names must start with a capital letter or they won’t work!
Without parentheses, any code on the lines after return will be ignored!
simple example:
// Write your component below!
function Congratulations(){
return(
<h1>Good Job!</h1>
);
}
export default function Test(){
return(
<div>
<Congratulations/>
<Congratulations/>
<Congratulations/>
</div>
);
}
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile src="https://tva1.sinaimg.cn/large/008vxvgGgy1h7r3olwjxsj30rs0knjsp.jpg" alt="test"/>
<Profile />
<Profile />
</section>
);
}
Passing Props to a Component
- Pass props to the child component
- Read props inside the child component
Don’t miss the pair of { and } curlies inside of ( and ) when declaring props:
function Avatar({ person, size }) {
// ...
}
Example:
// App.js
import { getImageUrl } from './utils.js';
function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
export default function Profile() {
return (
<div>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
<Avatar
size={80}
person={{
name: 'Aklilu Lemma',
imageId: 'OKS67lh'
}}
/>
<Avatar
size={50}
person={{
name: 'Lin Lanying',
imageId: '1bX5QH6'
}}
/>
</div>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
State and Lifecycle
Adding Lifecycle Methods to a Class
The order to call methods are:
- When <Clock /> is passed to root.render(), React calls the constructor of the Clock component. Since Clock needs to display the current time, it initializes this.state with an object including the current time. We will later update this state.
- React then calls the Clock component’s render() method. This is how React learns what should be displayed on the screen. React then updates the DOM to match the Clock’s render output.
- when the Clock output is inserted in the DOM, React calls the componentDidMount() lifecycle method. Inside it, the Clock component asks the browser to set up a timer to call the component’s tick() method once a second.
- Every second the browser calls the tick() method. Inside it, the Clock component schedules a UI update by calling setState() with an object containing the current time. Thanks to the setState() call, React knows the state has changed, and calls the render() method again to learn what should be on the screen. This time, this.state.date in the render() method will be different, and so the render output will include the updated time. React updates the DOM accordingly.
- If the Clock component is ever removed from the DOM, React calls the componentWillUnmount() lifecycle method so the timer is stopped.
Using setState() to update state
Handling Events
Responding to Events
To prevent the default form behavior of submitting, you can write:
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
Passing Arguments to Event Handlers
// method1
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
// method2, automatically passing event
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
Conditioanl Rendering
Element Variables
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>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<LoginControl />);
Inline If with Logical && Operator (不推荐)
Inline If-Else with Conditional Operator
Preventing Component from Rendering
Lists and Keys
we use the map() function to operate on an array of numbers.
Keys
- Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
- Keys will not be rendered in HTML DOM.
- We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. (Index as a key is an anti-pattern)
Extracting Components with Keys
For example, if you extract a ListItem component, you should keep the key on the <ListItem />
elements in the array rather than on the <li>
element in the ListItem itself.
Example: Incorrect Key Usage
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
Example: Correct Key Usage
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
Keys Must Only Be Unique Among Siblings
Keys used within arrays should be unique among their siblings. However, they don’t need to be globally unique
Forms
Controlled Components
通过value来实现受控组件
An input form element whose value is controlled by React in this way is called a “controlled component”.
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>
);
}
}
The textarea Tag
The select Tag
You can pass an array into the value attribute, allowing you to select multiple options in a select tag:
<select multiple={true} value={['B', 'C']}>
The file input Tag
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>
);
}
}
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(<FileInput />);
Handling Multiple Inputs
Controlled Input Null Value
Specifying the value prop on a controlled component prevents the user from changing the input unless you desire so. If you’ve specified a value but the input is still editable, you may have accidentally set value to undefined or null.
Lifting State Up
数据都是单向传递
props 传递到子级后,子级不能直接修改props。只能由父级自己修改自己的数据,然后再传递给子组件.
自己的状态只能由自己更改
- 数据正向传递: 父到子
- 数据反向传递: 父传递给子一个方法,子调用父的方法,并传入参数
虽然子组件无法直接访问和修改父组件的state,但是可以通过传入父组件的方法作为子组件的props去修改父组件的state。有点类似Java 中 private attribute和接口的感觉。
Example
function AddMsg(props){
return(
<button
onClick = {()=>props.AddNum(1)}
>
添加消息: {props.Num}
</button>
);
}
function DelMsg(props){
return(
<button
onClick = {()=>props.DelNum(1)}
>
删除消息: {props.Num}
</button>
);
}
class StateLifting extends React.Component{
constructor(props){
super(props);
this.state = {
Num: 0
};
}
AddNum = (num) =>{
this.setState(preState => {
return{
Num: preState.Num + num
}
});
}
DelNum = (num) =>{
this.setState(preState => {
return{
Num: preState.Num - num
}
});
}
render(){
return(
<div>
总消息条数是: {this.state.Num}
<hr/>
<AddMsg
Num = {this.state.Num}
AddNum = {this.AddNum}
/>
<DelMsg
Num = {this.state.Num}
DelNum = {this.DelNum}
/>
</div>
)
}
}
export default StateLifting
Sharing State Between Components
Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code.
Thinking in React
Example:
Imagine that we already have a JSON API and a mock from our designer. The mock looks like this:
<div style="text-align: center">
<img src = "https://reactjs.org/static/1071fbcc9eed01fddc115b41e193ec11/d4770/thinking-in-react-mock.png">
</div>
Our JSON API returns some data that looks like this:
[
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];
Step 1: Break The UI Into A Component Hierarchy
<div style="text-align: center">
<img src = "https://reactjs.org/static/9381f09e609723a8bb6e4ba1a7713b46/90cbd/thinking-in-react-components.png">
</div>
-
FilterableProductTable(Orange): contains the entirety of the example
- SearchBar(Blue): receives all user input
-
ProductTable(Green): displays and filters the data collection based on user input
- ProductCategoryRow(turquoise): displays a heading for each category
- ProductRow(red): displays a row for each product
Step 2: Build A Static Version in React
To build a static version of your app that renders your data model, you’ll want to build components that reuse other components and pass data using props. props are a way of passing data from parent to child. If you’re familiar with the concept of state, don’t use state at all to build this static version. State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app, you don’t need it.
You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy (i.e. starting with FilterableProductTable) or with the ones lower in it (ProductRow). In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up and write tests as you build.
Step 3: Identify The Minimal (but complete) Representation Of UI State
Let’s go through each one and figure out which one is state. Ask three questions about each piece of data:
- Is it passed in from a parent via props? If so, it probably isn’t state.
- Does it remain unchanged over time? If so, it probably isn’t state.
- Can you compute it based on any other state or props in your component? If so, it isn’t state.
So finally, our state is:
- The search text the user has entered
- The value of the checkbox
Step 4: Identify Where Your State Should Live
For each piece of state in your application:
- Identify every component that renders something based on that state.
- Find a common owner component (a single component above all the components that need the state in the hierarchy).
- Either the common owner or another component higher up in the hierarchy should own the state.
- If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
Step 5: Add Inverse Data Flow
So far, we’ve built an app that renders correctly as a function of props and state flowing down the hierarchy. Now it’s time to support data flowing the other way: the form components deep in the hierarchy need to update the state in FilterableProductTable.
React makes this data flow explicit to help you understand how your program works, but it does require a little more typing than traditional two-way data binding.
If you try to type or check the box in the previous version of the example (step 4), you’ll see that React ignores your input. This is intentional, as we’ve set the value prop of the input to always be equal to the state passed in from FilterableProductTable.
Let’s think about what we want to happen. We want to make sure that whenever the user changes the form, we update the state to reflect the user input. Since components should only update their own state, FilterableProductTable will pass callbacks to SearchBar that will fire whenever the state should be updated. We can use the onChange event on the inputs to be notified of it. The callbacks passed by FilterableProductTable will call setState(), and the app will be updated.