[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-70705":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":16,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":21,"defaultBranch":22,"hasWiki":20,"hasPages":21,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":17,"lastSyncTime":27,"discoverSource":28},70705,"TypeScript-React-Starter","microsoft\u002FTypeScript-React-Starter","microsoft","A starter template for TypeScript and React with a detailed README describing how to use the two together.",null,"TypeScript",11045,1205,7,120,0,1,2,44.24,"MIT License",true,false,"master",[],"2026-06-12 02:02:42","This repo is now deprecated. In the time since it created [TypeScript support](https:\u002F\u002Ffacebook.github.io\u002Fcreate-react-app\u002Fdocs\u002Fadding-typescript) is now a default feature of [Create React App](https:\u002F\u002Ffacebook.github.io\u002Fcreate-react-app\u002F), [Next.JS](https:\u002F\u002Fnextjs.org) and [Razzle](https:\u002F\u002Fgithub.com\u002Fjaredpalmer\u002Frazzle). \r\n\r\nThis means you can get started with:\r\n\r\n```sh\r\n# Creates an app called my-app\r\nnpx create-react-app my-app --typescript\r\n\r\ncd my-app\r\n\r\n# Adds the type definitions\r\nnpm install --save typescript @types\u002Fnode @types\u002Freact @types\u002Freact-dom @types\u002Fjest\r\n\r\necho \"Good to go :tada:\"\r\n```\r\n\r\nThis repo offers some exmples on how to take that project into production and handle testing and state. However, you can\r\nalso use the official documentation in the Create React App website for that. \r\n\r\nIf you'd like to know more about how to effectively do React with TypeScript, we recommend looking at the following:\r\n\r\n- [React+TypeScript Cheatsheets](https:\u002F\u002Fgithub.com\u002Ftypescript-cheatsheets\u002Freact-typescript-cheatsheet#reacttypescript-cheatsheets)\r\n- [React & Redux in TypeScript - Static Typing Guide](https:\u002F\u002Fgithub.com\u002Fpiotrwitek\u002Freact-redux-typescript-guide#react--redux-in-typescript---static-typing-guide)\r\n- [Use TypeScript to develop React applications](https:\u002F\u002Fegghead.io\u002Fcourses\u002Fuse-typescript-to-develop-react-applications)\r\n- [Ultimate React Component Patterns with Typescript 2.8](https:\u002F\u002Flevelup.gitconnected.com\u002Fultimate-react-component-patterns-with-typescript-2-8-82990c516935)\r\n\r\n\r\nBelow is the original README for this sample.\r\n\r\n---\r\n\r\n# TypeScript React Starter\r\n\r\nThis quick start guide will teach you how to wire up TypeScript with [React](http:\u002F\u002Ffacebook.github.io\u002Freact\u002F).\r\nBy the end, you'll have\r\n\r\n* a project with React and TypeScript\r\n* linting with [TSLint](https:\u002F\u002Fgithub.com\u002Fpalantir\u002Ftslint)\r\n* testing with [Jest](https:\u002F\u002Ffacebook.github.io\u002Fjest\u002F) and [Enzyme](http:\u002F\u002Fairbnb.io\u002Fenzyme\u002F), and\r\n* state management with [Redux](https:\u002F\u002Fgithub.com\u002Freactjs\u002Freact-redux)\r\n\r\nWe'll use the [create-react-app](https:\u002F\u002Fgithub.com\u002Ffacebookincubator\u002Fcreate-react-app) tool to quickly get set up.\r\n\r\nWe assume that you're already using [Node.js](https:\u002F\u002Fnodejs.org\u002F) with [npm](https:\u002F\u002Fwww.npmjs.com\u002F).\r\nYou may also want to get a sense of [the basics with React](https:\u002F\u002Ffacebook.github.io\u002Freact\u002Fdocs\u002Fhello-world.html).\r\n\r\n# Install create-react-app\r\n\r\nWe're going to use the create-react-app because it sets some useful tools and canonical defaults for React projects.\r\nThis is just a command-line utility to scaffold out new React projects.\r\n\r\n```shell\r\nnpm install -g create-react-app\r\n```\r\n\r\n# Create our new project\r\n\r\nWe'll create a new project called `my-app`:\r\n\r\n```shell\r\ncreate-react-app my-app --scripts-version=react-scripts-ts\r\n```\r\n\r\n[react-scripts-ts](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Freact-scripts-ts) is a set of adjustments to take the standard create-react-app project pipeline and bring TypeScript into the mix.\r\n\r\nAt this point, your project layout should look like the following:\r\n\r\n```text\r\nmy-app\u002F\r\n├─ .gitignore\r\n├─ images.d.ts\r\n├─ node_modules\u002F\r\n├─ public\u002F\r\n├─ src\u002F\r\n│  └─ ...\r\n├─ package.json\r\n├─ tsconfig.json\r\n├─ tsconfig.prod.json\r\n├─ tsconfig.test.json\r\n└─ tslint.json\r\n```\r\n\r\nOf note:\r\n\r\n* `tsconfig.json` contains TypeScript-specific options for our project.\r\n  * We also have a `tsconfig.prod.json` and a `tsconfig.test.json` in case we want to make any tweaks to our production builds, or our test builds.\r\n* `tslint.json` stores the settings that our linter, [TSLint](https:\u002F\u002Fgithub.com\u002Fpalantir\u002Ftslint), will use.\r\n* `package.json` contains our dependencies, as well as some shortcuts for commands we'd like to run for testing, previewing, and deploying our app.\r\n* `public` contains static assets like the HTML page we're planning to deploy to, or images. You can delete any file in this folder apart from `index.html`.\r\n* `src` contains our TypeScript and CSS code. `index.tsx` is the entry-point for our file, and is mandatory.\r\n* `images.d.ts` will tell TypeScript that certain types of image files can be `import`-ed, which create-react-app supports.\r\n\r\n# Setting up source control\r\n\r\nOur testing tool, Jest, expects some form of source control (such as Git or Mercurial) to be present.\r\nFor it to run correctly, we'll need to initialize a git repository.\r\n\r\n```sh\r\ngit init\r\ngit add .\r\ngit commit -m \"Initial commit.\"\r\n```\r\n\r\n> Note: if you've cloned this repository, you won't have to run the above at all.\r\n\r\n# Overriding defaults\r\n\r\nThe TSLint configuration that react-scripts-ts sets us up with is a bit overzealous.\r\nLet's fix that up.\r\n\r\n```diff\r\n {\r\n-  \"extends\": [\"tslint:recommended\", \"tslint-react\", \"tslint-config-prettier\"],\r\n+  \"extends\": [],\r\n+  \"defaultSeverity\": \"warning\",\r\n   \"linterOptions\": {\r\n     \"exclude\": [\r\n       \"config\u002F**\u002F*.js\",\r\n       \"node_modules\u002F**\u002F*.ts\"\r\n     ]\r\n   }\r\n }\r\n```\r\n\r\n[Configuring TSLint](https:\u002F\u002Fpalantir.github.io\u002Ftslint\u002Fusage\u002Fconfiguration\u002F) is out of the scope of this starter, but you should feel free to experiment with something that works for you.\r\n\r\n# Running the project\r\n\r\nRunning the project is as simple as running\r\n\r\n```sh\r\nnpm run start\r\n```\r\n\r\nThis runs the `start` script specified in our `package.json`, and will spawn off a server which reloads the page as we save our files.\r\nTypically the server runs at `http:\u002F\u002Flocalhost:3000`, but should be automatically opened for you.\r\n\r\nThis tightens the iteration loop by allowing us to quickly preview changes.\r\n\r\n# Testing the project\r\n\r\nTesting is also just a command away:\r\n\r\n```sh\r\nnpm run test\r\n```\r\n\r\nThis command runs Jest, an incredibly useful testing utility, against all files whose extensions end in `.test.ts` or `.spec.ts`.\r\nLike with the `npm run start` command, Jest will automatically run as soon as it detects changes.\r\nIf you'd like, you can run `npm run start` and `npm run test` side by side so that you can preview changes and test them simultaneously.\r\n\r\n# Creating a production build\r\n\r\nWhen running the project with `npm run start`, we didn't end up with an optimized build.\r\nTypically, we want the code we ship to users to be as fast and small as possible.\r\nCertain optimizations like minification can accomplish this, but often take more time.\r\nWe call builds like this \"production\" builds (as opposed to development builds).\r\n\r\nTo run a production build, just run\r\n\r\n```sh\r\nnpm run build\r\n```\r\n\r\nThis will create an optimized JS and CSS build in `.\u002Fbuild\u002Fstatic\u002Fjs` and `.\u002Fbuild\u002Fstatic\u002Fcss` respectively.\r\n\r\nYou won't need to run a production build most of the time,\r\nbut it is useful if you need to measure things like the final size of your app.\r\n\r\n# Creating a component\r\n\r\nWe're going to write a `Hello` component.\r\nThe component will take the name of whoever we want to greet (which we'll call `name`), and optionally, the number of exclamation marks to trail with (`enthusiasmLevel`).\r\n\r\nWhen we write something like `\u003CHello name=\"Daniel\" enthusiasmLevel={3} \u002F>`, the component should render to something like `\u003Cdiv>Hello Daniel!!!\u003C\u002Fdiv>`.\r\nIf `enthusiasmLevel` isn't specified, the component should default to showing one exclamation mark.\r\nIf `enthusiasmLevel` is `0` or negative, it should throw an error.\r\n\r\nWe'll write a `Hello.tsx`:\r\n\r\n```ts\r\n\u002F\u002F src\u002Fcomponents\u002FHello.tsx\r\n\r\nimport * as React from 'react';\r\n\r\nexport interface Props {\r\n  name: string;\r\n  enthusiasmLevel?: number;\r\n}\r\n\r\nfunction Hello({ name, enthusiasmLevel = 1 }: Props) {\r\n  if (enthusiasmLevel \u003C= 0) {\r\n    throw new Error('You could be a little more enthusiastic. :D');\r\n  }\r\n\r\n  return (\r\n    \u003Cdiv className=\"hello\">\r\n      \u003Cdiv className=\"greeting\">\r\n        Hello {name + getExclamationMarks(enthusiasmLevel)}\r\n      \u003C\u002Fdiv>\r\n    \u003C\u002Fdiv>\r\n  );\r\n}\r\n\r\nexport default Hello;\r\n\r\n\u002F\u002F helpers\r\n\r\nfunction getExclamationMarks(numChars: number) {\r\n  return Array(numChars + 1).join('!');\r\n}\r\n```\r\n\r\nNotice that we defined a type named `Props` that specifies the properties our component will take.\r\n`name` is a required `string`, and `enthusiasmLevel` is an optional `number` (which you can tell from the `?` that we wrote out after its name).\r\n\r\nWe also wrote `Hello` as a stateless function component (an SFC).\r\nTo be specific, `Hello` is a function that takes a `Props` object, and picks apart (or \"destructures\") all the properties that it will be passed.\r\nIf `enthusiasmLevel` isn't given in our `Props` object, it will default to `1`.\r\n\r\nWriting functions is one of two primary [ways React allows us to make components]((https:\u002F\u002Ffacebook.github.io\u002Freact\u002Fdocs\u002Fcomponents-and-props.html#functional-and-class-components)).\r\nIf we wanted, we *could* have written it out as a class as follows:\r\n\r\n```ts\r\nclass Hello extends React.Component\u003CProps, object> {\r\n  render() {\r\n    const { name, enthusiasmLevel = 1 } = this.props;\r\n\r\n    if (enthusiasmLevel \u003C= 0) {\r\n      throw new Error('You could be a little more enthusiastic. :D');\r\n    }\r\n\r\n    return (\r\n      \u003Cdiv className=\"hello\">\r\n        \u003Cdiv className=\"greeting\">\r\n          Hello {name + getExclamationMarks(enthusiasmLevel)}\r\n        \u003C\u002Fdiv>\r\n      \u003C\u002Fdiv>\r\n    );\r\n  }\r\n}\r\n```\r\n\r\nClasses are useful [when our component instances have some state or need to handle lifecycle hooks](https:\u002F\u002Ffacebook.github.io\u002Freact\u002Fdocs\u002Fstate-and-lifecycle.html).\r\nBut we don't really need to think about state in this specific example - in fact, we specified it as `object` in `React.Component\u003CProps, object>`, so writing an SFC makes more sense here, but it's important to know how to write a class component.\r\n\r\nNotice that the class extends `React.Component\u003CProps, object>`.\r\nThe TypeScript-specific bit here are the type arguments we're passing to `React.Component`: `Props` and `object`.\r\nHere, `Props` is the type of our class's `this.props`, and `object` is the type of `this.state`.\r\nWe'll return to component state in a bit.\r\n\r\nNow that we've written our component, let's dive into `index.tsx` and replace our render of `\u003CApp \u002F>` with a render of `\u003CHello ... \u002F>`.\r\n\r\nFirst we'll import it at the top of the file:\r\n\r\n```ts\r\nimport Hello from '.\u002Fcomponents\u002FHello';\r\n```\r\n\r\nand then change up our `render` call:\r\n\r\n```ts\r\nReactDOM.render(\r\n  \u003CHello name=\"TypeScript\" enthusiasmLevel={10} \u002F>,\r\n  document.getElementById('root') as HTMLElement\r\n);\r\n```\r\n\r\n## Type assertions\r\n\r\nOne thing we'll point out in this section is the line `document.getElementById('root') as HTMLElement`.\r\nThis syntax is called a *type assertion*, sometimes also called a *cast*.\r\nThis is a useful way of telling TypeScript what the real type of an expression is when you know better than the type checker.\r\n\r\nThe reason we need to do so in this case is that `getElementById`'s return type is `HTMLElement | null`.\r\nPut simply, `getElementById` returns `null` when it can't find an element with a given `id`.\r\nWe're assuming that `getElementById` will actually succeed, so we need to convince TypeScript of that using the `as` syntax.\r\n\r\nTypeScript also has a trailing \"bang\" syntax (`!`), which removes `null` and `undefined` from the prior expression.\r\nSo we *could* have written `document.getElementById('root')!`, but in this case we wanted to be a bit more explicit.\r\n\r\n## Stateful components\r\n\r\nWe mentioned earlier that our component didn't need state.\r\nWhat if we wanted to be able to update our components based on user interaction over time?\r\nAt that point, state becomes more important.\r\n\r\nDeeply understanding best practices around component state in React are out of the scope of this starter, but let's quickly peek at a *stateful* version of our `Hello` component to see what adding state looks like.\r\nWe're going to render two `\u003Cbutton>`s which update the number of exclamation marks that a `Hello` component displays.\r\n\r\nTo do that, we're going to\r\n\r\n1. Define a type for our state (i.e. `this.state`)\r\n1. Initialize `this.state` based on the props we're given in our constructor.\r\n1. Create two event handlers for our buttons (`onIncrement` and `onDecrement`).\r\n\r\n```ts\r\n\u002F\u002F src\u002Fcomponents\u002FStatefulHello.tsx\r\n\r\nimport * as React from \"react\";\r\n\r\nexport interface Props {\r\n  name: string;\r\n  enthusiasmLevel?: number;\r\n}\r\n\r\ninterface State {\r\n  currentEnthusiasm: number;\r\n}\r\n\r\nclass Hello extends React.Component\u003CProps, State> {\r\n  constructor(props: Props) {\r\n    super(props);\r\n    this.state = { currentEnthusiasm: props.enthusiasmLevel || 1 };\r\n  }\r\n\r\n  onIncrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm + 1);\r\n  onDecrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm - 1);\r\n\r\n  render() {\r\n    const { name } = this.props;\r\n\r\n    if (this.state.currentEnthusiasm \u003C= 0) {\r\n      throw new Error('You could be a little more enthusiastic. :D');\r\n    }\r\n\r\n    return (\r\n      \u003Cdiv className=\"hello\">\r\n        \u003Cdiv className=\"greeting\">\r\n          Hello {name + getExclamationMarks(this.state.currentEnthusiasm)}\r\n        \u003C\u002Fdiv>\r\n        \u003Cbutton onClick={this.onDecrement}>-\u003C\u002Fbutton>\r\n        \u003Cbutton onClick={this.onIncrement}>+\u003C\u002Fbutton>\r\n      \u003C\u002Fdiv>\r\n    );\r\n  }\r\n\r\n  updateEnthusiasm(currentEnthusiasm: number) {\r\n    this.setState({ currentEnthusiasm });\r\n  }\r\n}\r\n\r\nexport default Hello;\r\n\r\nfunction getExclamationMarks(numChars: number) {\r\n  return Array(numChars + 1).join('!');\r\n}\r\n```\r\n\r\nNotice:\r\n\r\n1. Much like with `Props`, we had to define a new type for our state: `State`.\r\n1. To update state in React, we use `this.setState` - we don't set it directly except in the constructor. `setState` only takes the properties we're interested in updating and our component will re-render as appropriate.\r\n1. We're using class property initializers with arrow functions (e.g. `onIncrement = () => ...`).\r\n  * Declaring these as arrow functions avoids issues with orphaned uses of `this`.\r\n  * Setting them as instance properties creates them only once - a common mistake is to initialize them in the `render` method which allocates closures one every call to `render`.\r\n\r\nWe won't use this stateful component any further in this starter.\r\nStateful components are great for creating components that focus solely on presenting content (as opposed to handling core application state).\r\nIn some contexts, it can be used for handling your entire application's state, with one central component passing down functions that can call `setState` appropriately; however, for much larger applications, a dedicated state manager might be preferable (as we'll discuss below).\r\n\r\n# Adding style 😎\r\n\r\nStyling a component with our setup is easy.\r\nTo style our `Hello` component, we can create a CSS file at `src\u002Fcomponents\u002FHello.css`.\r\n\r\n```css\r\n.hello {\r\n  text-align: center;\r\n  margin: 20px;\r\n  font-size: 48px;\r\n  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\r\n}\r\n\r\n.hello button {\r\n  margin-left: 25px;\r\n  margin-right: 25px;\r\n  font-size: 40px;\r\n  min-width: 50px;\r\n}\r\n```\r\n\r\nThe tools that create-react-app uses (namely, Webpack and various loaders) allow us to just import the stylesheets we're interested in.\r\nWhen our build runs, any imported `.css` files will be concatenated into an output file.\r\nSo in `src\u002Fcomponents\u002FHello.tsx`, we'll add the following import.\r\n\r\n```ts\r\nimport '.\u002FHello.css';\r\n```\r\n\r\n# Writing tests with Jest\r\n\r\nWe had a certain set of assumptions about our `Hello` component.\r\nLet's reiterate what they were:\r\n\r\n> * When we write something like `\u003CHello name=\"Daniel\" enthusiasmLevel={3} \u002F>`, the component should render to something like `\u003Cdiv>Hello Daniel!!!\u003C\u002Fdiv>`.\r\n> * If `enthusiasmLevel` isn't specified, the component should default to showing one exclamation mark.\r\n> * If `enthusiasmLevel` is `0` or negative, it should throw an error.\r\n\r\nWe can use these requirements to write a few tests for our components.\r\n\r\nBut first, let's install Enzyme.\r\n[Enzyme](http:\u002F\u002Fairbnb.io\u002Fenzyme\u002F) is a common tool in the React ecosystem that makes it easier to write tests for how components will behave.\r\nBy default, our application includes a library called jsdom to allow us to simulate the DOM and test its runtime behavior without a browser.\r\nEnzyme is similar, but builds on jsdom and makes it easier to make certain queries about our components.\r\n\r\nLet's install it as a development-time dependency.\r\n\r\n```sh\r\nnpm install -D enzyme @types\u002Fenzyme enzyme-adapter-react-16 @types\u002Fenzyme-adapter-react-16 react-test-renderer\r\n```\r\n\r\nNotice we installed packages `enzyme` as well as `@types\u002Fenzyme`.\r\nThe `enzyme` package refers to the package containing JavaScript code that actually gets run, while `@types\u002Fenzyme` is a package that contains declaration files (`.d.ts` files) so that TypeScript can understand how you can use Enzyme.\r\nYou can learn more about `@types` packages [here](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fdeclaration-files\u002Fconsumption.html).\r\n\r\nWe also had to install `enzyme-adapter-react-16 and react-test-renderer`.\r\nThis is something `enzyme` expects to be installed.\r\n\r\nBefore writing the first test, we have to configure Enzyme to use an adapter for React 16.\r\nWe'll create a file called `src\u002FsetupTests.ts` that is automatically loaded when running tests:\r\n\r\n```ts\r\nimport * as enzyme from 'enzyme';\r\nimport * as Adapter from 'enzyme-adapter-react-16';\r\n\r\nenzyme.configure({ adapter: new Adapter() });\r\n```\r\n\r\nNow that we've got Enzyme set up, let's start writing our test!\r\nLet's create a file named `src\u002Fcomponents\u002FHello.test.tsx`, adjacent to our `Hello.tsx` file from earlier.\r\n\r\n```ts\r\n\u002F\u002F src\u002Fcomponents\u002FHello.test.tsx\r\n\r\nimport * as React from 'react';\r\nimport * as enzyme from 'enzyme';\r\nimport Hello from '.\u002FHello';\r\n\r\nit('renders the correct text when no enthusiasm level is given', () => {\r\n  const hello = enzyme.shallow(\u003CHello name='Daniel' \u002F>);\r\n  expect(hello.find(\".greeting\").text()).toEqual('Hello Daniel!')\r\n});\r\n\r\nit('renders the correct text with an explicit enthusiasm of 1', () => {\r\n  const hello = enzyme.shallow(\u003CHello name='Daniel' enthusiasmLevel={1}\u002F>);\r\n  expect(hello.find(\".greeting\").text()).toEqual('Hello Daniel!')\r\n});\r\n\r\nit('renders the correct text with an explicit enthusiasm level of 5', () => {\r\n  const hello = enzyme.shallow(\u003CHello name='Daniel' enthusiasmLevel={5} \u002F>);\r\n  expect(hello.find(\".greeting\").text()).toEqual('Hello Daniel!!!!!');\r\n});\r\n\r\nit('throws when the enthusiasm level is 0', () => {\r\n  expect(() => {\r\n    enzyme.shallow(\u003CHello name='Daniel' enthusiasmLevel={0} \u002F>);\r\n  }).toThrow();\r\n});\r\n\r\nit('throws when the enthusiasm level is negative', () => {\r\n  expect(() => {\r\n    enzyme.shallow(\u003CHello name='Daniel' enthusiasmLevel={-1} \u002F>);\r\n  }).toThrow();\r\n});\r\n```\r\n\r\nThese tests are extremely basic, but you should be able to get the gist of things.\r\n\r\n# Adding state management\r\n\r\nAt this point, if all you're using React for is fetching data once and displaying it, you can consider yourself done.\r\nBut if you're developing an app that's more interactive, then you may need to add state management.\r\n\r\n## State management in general\r\n\r\nOn its own, React is a useful library for creating composable views.\r\nHowever, React doesn't prescribe any specific way of synchronizing data throughout your application.\r\nAs far as a React component is concerned, data flows down through its children through the props you specify on each element.\r\nSome of those props might be functions that update the state one way or another, but how that happens is an open question.\r\n\r\nBecause React on its own does not focus on application state management, the React community uses libraries like Redux and MobX.\r\n\r\n[Redux](http:\u002F\u002Fredux.js.org) relies on synchronizing data through a centralized and immutable store of data, and updates to that data will trigger a re-render of our application.\r\nState is updated in an immutable fashion by sending explicit action messages which must be handled by functions called reducers.\r\nBecause of the explicit nature, it is often easier to reason about how an action will affect the state of your program.\r\n\r\n[MobX](https:\u002F\u002Fmobx.js.org\u002F) relies on functional reactive patterns where state is wrapped through observables and passed through as props.\r\nKeeping state fully synchronized for any observers is done by simply marking state as observable.\r\nAs a nice bonus, the library is already written in TypeScript.\r\n\r\nThere are various merits and tradeoffs to both.\r\nGenerally Redux tends to see more widespread usage, so for the purposes of this tutorial, we'll focus on adding Redux;\r\nhowever, you should feel encouraged to explore both.\r\n\r\nThe following section may have a steep learning curve.\r\nWe strongly suggest you [familiarize yourself with Redux through its documentation](http:\u002F\u002Fredux.js.org\u002F).\r\n\r\n## Setting the stage for actions\r\n\r\nIt doesn't make sense to add Redux unless the state of our application changes.\r\nWe need a source of actions that will trigger changes to take place.\r\nThis can be a timer, or something in the UI like a button.\r\n\r\nFor our purposes, we're going to add two buttons to control the enthusiasm level for our `Hello` component.\r\n\r\n## Installing Redux\r\n\r\nTo add Redux, we'll first install `redux` and `react-redux`, as well as their types, as a dependency.\r\n\r\n```sh\r\nnpm install -S redux react-redux @types\u002Freact-redux\r\n```\r\n\r\nIn this case we didn't need to install `@types\u002Fredux` because Redux already comes with its own definition files (`.d.ts` files).\r\n\r\n## Defining our app's state\r\n\r\nWe need to define the shape of the state which Redux will store.\r\nFor this, we can create a file called `src\u002Ftypes\u002Findex.tsx` which will contain definitions for types that we might use throughout the program.\r\n\r\n```ts\r\n\u002F\u002F src\u002Ftypes\u002Findex.tsx\r\n\r\nexport interface StoreState {\r\n    languageName: string;\r\n    enthusiasmLevel: number;\r\n}\r\n```\r\n\r\nOur intention is that `languageName` will be the programming language this app was written in (i.e. TypeScript or JavaScript) and `enthusiasmLevel` will vary.\r\nWhen we write our first container, we'll understand why we intentionally made our state slightly different from our props.\r\n\r\n## Adding actions\r\n\r\nLet's start off by creating a set of message types that our app can respond to in `src\u002Fconstants\u002Findex.tsx`.\r\n\r\n```ts\r\n\u002F\u002F src\u002Fconstants\u002Findex.tsx\r\n\r\nexport const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';\r\nexport type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;\r\n\r\n\r\nexport const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';\r\nexport type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;\r\n```\r\n\r\nThis `const`\u002F`type` pattern allows us to use TypeScript's string literal types in an easily accessible and refactorable way.\r\n\r\nNext, we'll create a set of actions and functions that can create these actions in `src\u002Factions\u002Findex.tsx`.\r\n\r\n```ts\r\nimport * as constants from '..\u002Fconstants';\r\n\r\nexport interface IncrementEnthusiasm {\r\n    type: constants.INCREMENT_ENTHUSIASM;\r\n}\r\n\r\nexport interface DecrementEnthusiasm {\r\n    type: constants.DECREMENT_ENTHUSIASM;\r\n}\r\n\r\nexport type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;\r\n\r\nexport function incrementEnthusiasm(): IncrementEnthusiasm {\r\n    return {\r\n        type: constants.INCREMENT_ENTHUSIASM\r\n    }\r\n}\r\n\r\nexport function decrementEnthusiasm(): DecrementEnthusiasm {\r\n    return {\r\n        type: constants.DECREMENT_ENTHUSIASM\r\n    }\r\n}\r\n```\r\n\r\nWe've created two types that describe what increment actions and decrement actions should look like.\r\nWe also created a type (`EnthusiasmAction`) to describe cases where an action could be an increment or a decrement.\r\nFinally, we made two functions that actually manufacture the actions which we can use instead of writing out bulky object literals.\r\n\r\nThere's clearly boilerplate here, so you should feel free to look into libraries like [redux-actions](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fredux-actions) once you've got the hang of things.\r\n\r\n## Adding a reducer\r\n\r\nWe're ready to write our first reducer!\r\nReducers are just functions that generate changes by creating modified copies of our application's state, but that have *no side effects*.\r\nIn other words, they're what we call *[pure functions](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPure_function)*.\r\n\r\nOur reducer will go under `src\u002Freducers\u002Findex.tsx`.\r\nIts function will be to ensure that increments raise the enthusiasm level by 1, and that decrements reduce the enthusiasm level by 1, but that the level never falls below 1.\r\n\r\n```ts\r\n\u002F\u002F src\u002Freducers\u002Findex.tsx\r\n\r\nimport { EnthusiasmAction } from '..\u002Factions';\r\nimport { StoreState } from '..\u002Ftypes\u002Findex';\r\nimport { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '..\u002Fconstants\u002Findex';\r\n\r\nexport function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {\r\n  switch (action.type) {\r\n    case INCREMENT_ENTHUSIASM:\r\n      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };\r\n    case DECREMENT_ENTHUSIASM:\r\n      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };\r\n  }\r\n  return state;\r\n}\r\n```\r\n\r\nNotice that we're using the *object spread* (`...state`) which allows us to create a shallow copy of our state, while replacing the `enthusiasmLevel`.\r\nIt's important that the `enthusiasmLevel` property come last, since otherwise it would be overridden by the property in our old state.\r\n\r\nYou may want to write a few tests for your reducer.\r\nSince reducers are pure functions, they can be passed arbitrary data.\r\nFor every input, reducers can be tested by checking their newly produced state.\r\nConsider looking into Jest's [toEqual](https:\u002F\u002Ffacebook.github.io\u002Fjest\u002Fdocs\u002Fexpect.html#toequalvalue) method to accomplish this.\r\n\r\n## Making a container\r\n\r\nWhen writing with Redux, we will often write components as well as containers.\r\nComponents are often data-agnostic, and work mostly at a presentational level.\r\n*Containers* typically wrap components and feed them any data that is necessary to display and modify state.\r\nYou can read more about this concept on [Dan Abramov's article *Presentational and Container Components*](https:\u002F\u002Fmedium.com\u002F@dan_abramov\u002Fsmart-and-dumb-components-7ca2f9a7c7d0).\r\n\r\nFirst let's update `src\u002Fcomponents\u002FHello.tsx` so that it can modify state.\r\nWe'll add two optional callback properties to `Props` named `onIncrement` and `onDecrement`:\r\n\r\n```ts\r\nexport interface Props {\r\n  name: string;\r\n  enthusiasmLevel?: number;\r\n  onIncrement?: () => void;\r\n  onDecrement?: () => void;\r\n}\r\n```\r\n\r\nThen we'll bind those callbacks to two new buttons that we'll add into our component.\r\n\r\n```ts\r\nfunction Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {\r\n  if (enthusiasmLevel \u003C= 0) {\r\n    throw new Error('You could be a little more enthusiastic. :D');\r\n  }\r\n\r\n  return (\r\n    \u003Cdiv className=\"hello\">\r\n      \u003Cdiv className=\"greeting\">\r\n        Hello {name + getExclamationMarks(enthusiasmLevel)}\r\n      \u003C\u002Fdiv>\r\n      \u003Cdiv>\r\n        \u003Cbutton onClick={onDecrement}>-\u003C\u002Fbutton>\r\n        \u003Cbutton onClick={onIncrement}>+\u003C\u002Fbutton>\r\n      \u003C\u002Fdiv>\r\n    \u003C\u002Fdiv>\r\n  );\r\n}\r\n```\r\n\r\nIn general, it'd be a good idea to write a few tests for `onIncrement` and `onDecrement` being triggered when their respective buttons are clicked.\r\nGive it a shot to get the hang of writing tests for your components.\r\n\r\nNow that our component is updated, we're ready to wrap it into a container.\r\nLet's create a file named `src\u002Fcontainers\u002FHello.tsx` and start off with the following imports.\r\n\r\n```ts\r\nimport Hello from '..\u002Fcomponents\u002FHello';\r\nimport * as actions from '..\u002Factions\u002F';\r\nimport { StoreState } from '..\u002Ftypes\u002Findex';\r\nimport { connect, Dispatch } from 'react-redux';\r\n```\r\n\r\nThe real two key pieces here are the original `Hello` component as well as the `connect` function from react-redux.\r\n`connect` will be able to actually take our original `Hello` component and turn it into a container using two functions:\r\n\r\n* `mapStateToProps` which massages the data from the current store to part of the shape that our component needs.\r\n* `mapDispatchToProps` which creates callback props to pump actions to our store using a given `dispatch` function.\r\n\r\nIf we recall, our application state consists of two properties: `languageName` and `enthusiasmLevel`.\r\nOur `Hello` component, on the other hand, expected a `name` and an `enthusiasmLevel`.\r\n`mapStateToProps` will get the relevant data from the store, and adjust it if necessary, for our component's props.\r\nLet's go ahead and write that.\r\n\r\n```ts\r\nexport function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {\r\n  return {\r\n    enthusiasmLevel,\r\n    name: languageName,\r\n  }\r\n}\r\n```\r\n\r\nNote that `mapStateToProps` only creates 2 out of 4 of the properties a `Hello` component expects.\r\nNamely, we still want to pass in the `onIncrement` and `onDecrement` callbacks.\r\n`mapDispatchToProps` is a function that takes a dispatcher function.\r\nThis dispatcher function can pass actions into our store to make updates, so we can create a pair of callbacks that will call the dispatcher as necessary.\r\n\r\n```ts\r\nexport function mapDispatchToProps(dispatch: Dispatch\u003Cactions.EnthusiasmAction>) {\r\n  return {\r\n    onIncrement: () => dispatch(actions.incrementEnthusiasm()),\r\n    onDecrement: () => dispatch(actions.decrementEnthusiasm()),\r\n  }\r\n}\r\n```\r\n\r\nFinally, we're ready to call `connect`.\r\n`connect` will first take `mapStateToProps` and `mapDispatchToProps`, and then return another function that we can use to wrap our component.\r\nOur resulting container is defined with the following line of code:\r\n\r\n```ts\r\nexport default connect(mapStateToProps, mapDispatchToProps)(Hello);\r\n```\r\n\r\nWhen we're finished, our file should look like this:\r\n\r\n```ts\r\n\u002F\u002F src\u002Fcontainers\u002FHello.tsx\r\n\r\nimport Hello from '..\u002Fcomponents\u002FHello';\r\nimport * as actions from '..\u002Factions\u002F';\r\nimport { StoreState } from '..\u002Ftypes\u002Findex';\r\nimport { connect, Dispatch } from 'react-redux';\r\n\r\nexport function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {\r\n  return {\r\n    enthusiasmLevel,\r\n    name: languageName,\r\n  }\r\n}\r\n\r\nexport function mapDispatchToProps(dispatch: Dispatch\u003Cactions.EnthusiasmAction>) {\r\n  return {\r\n    onIncrement: () => dispatch(actions.incrementEnthusiasm()),\r\n    onDecrement: () => dispatch(actions.decrementEnthusiasm()),\r\n  }\r\n}\r\n\r\nexport default connect(mapStateToProps, mapDispatchToProps)(Hello);\r\n```\r\n\r\n## Creating a store\r\n\r\nLet's go back to `src\u002Findex.tsx`.\r\nTo put this all together, we need to create a store with an initial state, and set it up with all of our reducers.\r\n\r\n```ts\r\nimport { createStore } from 'redux';\r\nimport { enthusiasm } from '.\u002Freducers\u002Findex';\r\nimport { StoreState } from '.\u002Ftypes\u002Findex';\r\n\r\nconst store = createStore\u003CStoreState>(enthusiasm, {\r\n  enthusiasmLevel: 1,\r\n  languageName: 'TypeScript',\r\n});\r\n```\r\n\r\n`store` is, as you might've guessed, our central store for our application's global state.\r\n\r\nNext, we're going to swap our use of `.\u002Fsrc\u002Fcomponents\u002FHello` with `.\u002Fsrc\u002Fcontainers\u002FHello` and use react-redux's `Provider` to wire up our props with our container.\r\nWe'll import each:\r\n\r\n```ts\r\nimport Hello from '.\u002Fcontainers\u002FHello';\r\nimport { Provider } from 'react-redux';\r\n```\r\n\r\nand pass our `store` through to the `Provider`'s attributes:\r\n\r\n```ts\r\nReactDOM.render(\r\n  \u003CProvider store={store}>\r\n    \u003CHello \u002F>\r\n  \u003C\u002FProvider>,\r\n  document.getElementById('root') as HTMLElement\r\n);\r\n```\r\n\r\nNotice that `Hello` no longer needs props, since we used our `connect` function to adapt our application's state for our wrapped `Hello` component's props.\r\n\r\n# Ejecting\r\n\r\nIf at any point, you feel like there are certain customizations that the create-react-app setup has made difficult, you can always opt-out and get the various configuration options you need.\r\nFor example, if you'd like to add a Webpack plugin, it might be necessary to take advantage of the \"eject\" functionality that create-react-app provides.\r\n\r\nSimply run\r\n\r\n```sh\r\nnpm run eject\r\n```\r\n\r\nand you should be good to go!\r\n\r\nAs a heads up, you may want to commit all your work before running an eject.\r\nYou cannot undo an eject command, so opting out is permanent unless you can recover from a commit prior to running an eject.\r\n\r\n# Next steps\r\n\r\ncreate-react-app comes with a lot of great stuff.\r\nMuch of it is documented in the default `README.md` that was generated for our project, so give that a quick read.\r\n\r\nIf you still want to learn more about Redux, you can [check out the official website](http:\u002F\u002Fredux.js.org\u002F) for documentation.\r\nThe same goes [for MobX](https:\u002F\u002Fmobx.js.org\u002F).\r\n\r\nIf you want to eject at some point, you may need to know a little bit more about Webpack.\r\nYou can check out our [React & Webpack walkthrough here](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Freact-&-webpack.html).\r\n\r\nAt some point you might need routing.\r\nThere are several solutions, but [react-router](https:\u002F\u002Fgithub.com\u002FReactTraining\u002Freact-router) is probably the most popular for Redux projects, and is often used in conjunction with [react-router-redux](https:\u002F\u002Fgithub.com\u002Freactjs\u002Freact-router-redux).\r\n","该项目提供了一个结合TypeScript和React的启动模板，并附带详细的README文档说明如何将两者一起使用。它支持项目初始化、类型检查（通过TSLint）、单元测试（使用Jest与Enzyme）以及状态管理（采用Redux）。尽管当前官方推荐使用Create React App等现代工具直接创建支持TypeScript的React应用，但此仓库仍为希望深入了解React与TypeScript集成细节的开发者提供了宝贵资源。适合于需要快速搭建具备良好开发体验基础架构的前端项目场景，特别是那些计划利用静态类型优势提高代码质量的团队或个人。","2026-06-11 03:33:43","high_star"]