function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
<head>
<link rel="stylesheet" type="text/css" href="theme.css">
</head>
// or
import '../componetn.css' // sass, less...
<button className='btn'></button>
<button className='btn btn-primary'></button>
const Button = ({classes, children}) => (
<button className={classes.button}>
{children}
</button>
);
const styles = theme => ({
button: {
background: theme.colorPrimary
},
label: { // useless
fontWeight: 'bold'
}
});
const StyledButton = injectSheet(styles)(Button);
const theme = {
colorPrimary: 'green',
animationDuration: 300,
};
const App = () => (
<ThemeProvider theme={theme}>
<StyledButton>I am a button</StyledButton>
</ThemeProvider>
);
(emotion, glamorous)
const ContactItem = styled.div`
// ... usual CSS
flex-direction: ${({ isHeader }) =>
(isHeader ? 'row-reverse' : 'row')};
${({ isHeader }) => !isHeader && 'padding: 10px 0;'}
`;
const ContactItemComponent = ({
...
isHeader,
isChat
}) => {
const styleProps = { isHeader, isChat };
return (
<ContactItem {...styleProps}>
...
</ContactItem>
);
};
const Textarea = styled.textarea`
line-height:
${({lines}) => lines === 1 ? 26 : 16}px;
height:
${({lines}) => lines === 1 ? 26 : lines * 16}px;
`;
class ChatInput extends Component {
...
stateHandler ({ target: { value } }) {
const lines = value.split(/\r*\n/).length;
this.setState({message: value, lines})
}
render() {
return <Textarea
lines={this.state.lines}
onChange={this.stateHandler.bind(this)}
/>
}
}
const Routes = [
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'heroes',
component: HeroesComponent
children: [
{
path: 'view/:id',
component: HeroDetailComponent
},
{
path: 'edit/:id',
component: HeroEditComponent
},
]
},
];
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
const HeaderComponent = ({ title, contacts }) => {
return (
<Header>
<Route
path="/chats/:id"
children={({ match }) => {
if (match) {
const { params: { id } } = match;
return <ChatHeaderComponent data={contacts[id]} />;
} else { // match is undefined
return <Title>{title}</Title>;
}
}}
/>
</Header>
);
};
export default connect(stateToProps)(HeaderComponent);
const HeaderComponent = ({ match, title, contacts }) => {
return (
<Header>
{match.path === '/chats/:id' ?
<ChatHeaderComponent data={contacts[Number(id)]} />
:
<Title>{title}</Title>
}
</Header>
);
};
export default withRouter(HeaderComponent);
const ANIMATION_DURATION = 300;
const Animation = styled.div`
transform: translate3d(100%, 0, 0);
transition: transform ${ANIMATION_DURATION}ms;
&.is-animate {
transform: translate3d(0, 0, 0);
}
`;
class ChatTransition extends Component {
state = {
complete: false,
animate: false,
};
componentDidMount() {
setTimeout(() =>
this.setState({ animate: true }), 5);
setTimeout(() =>
this.setState({ complete: true }),
ANIMATION_DURATION
);
}
render() {
if (this.state.complete) {
return <Redirect to={this.props.to} />;
} else {
return
<Animation
className={this.state.animate ? 'is-animate' : ''}
>
<ChatContent/>
</Animation>
}
}
}
const AnimatedRoute = ({ component, render, children, ...props }) => (
<Route
{...props}
render={() =>
<Animation>
<Route {...props}
component={component}
render={render}
children={children}
/>
</Animation>
}
/>
);
<Router>
<Switch>
{routes.map(({ to, component }, i) => (
<AnimatedRoute key={i} path={to} component={component} /> // key is required
))}
</Switch>
</Router>
Usage
const Chat = ({ match: { params: { id } } }) => (
<Layout>
<ScrollToTopOnMount/>
<ChatContent id={id} />
<ChatInput key={2} />
</Layout>
);
class ScrollToTopOnMount extends Component {
componentDidMount() {
window.scrollTo(0, 0)
}
render() {
return null
}
}
Parse markup
and CSS
Create render tree
Reflow and Repaint
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences.
class ContactItemPureComponent extends PureComponent {
render() {
const { name, imgPath, ...} = this.props;
return (
<ContactItem
name={name}
imgPath={imgPath}
/>
);
}
}
export default ContactItemPureComponent;
!shallowEqual(prevProps, nextProps) ||
!shallowEqual(inst.state, nextState)
Technically, a container component is just a React component that uses store.subscribe() to read a part of the Redux state tree and supply props to a presentational component it renders.
const ContactItemWithConnect = ({ id, name, ... }) => (
<ContactItem
name={name}
...
/>
);
export default connect(
({ contacts: { data } }, { id }) => ({ ...data[id] }))
(ContactItemWithConnect);
// Contacts
[
{
id: 1,
imgPath: 'vboiko.jpg',
name:'Boiko Volodymyr',
isOnline: true,
lastSeen: '21.06.2017'
},
{
id: 2,
...
},
{
id: 3,
...
}
]
// Contacts
{
data: {
1: {
imgPath: 'vboiko.jpg',
name:'Boiko Volodymyr',
isOnline: true,
lastSeen: '21.06.2017'
},
2: {...},
3: {...},
},
list: [1, 2, 3, n]
}
Anytime any part of the state tree changes, TodoApp is gonna get rerendered
export default connect(state => state)(TodoApp)
As of React 16, react-addons-perf is not supported.
import Perf from 'react-addons-perf';
window.Perf = Perf;
// in console
Perf.start();
Perf.stop();
Perf.pringWasted();
// in Component
componentWillMout() {
Perf.start()
};
componentWillUnmout() {
Perf.stop()
Perf.pringWasted()
}
The User Timing interface allows the developer to create application specific timestamps that are part of the browser's performance timeline.