In general, declare in the bash shell sets (or removes) attributes on variables. An attribute is a sort of annotation that says "this is a name reference", or "this is an associative array", or "this variable should always be evaluated as an integer", or "this variable is read-only and can not be re-set", or "this variable is exported (an environment variable)" etc.
The typeset built-in is a synonym for declare in bash, as typeset is used in other shells (ksh, where it originated, and zsh, for example) for setting variable attributes.
Looking more closely at the name reference example in the question:
The shell function that you show, with an added bit of code that uses it:
The value hello is then available in $foo in the main part of the script.
In general, declare in the bash shell sets (or removes) attributes on variables. An attribute is a sort of annotation that says "this is a name reference", or "this is an associative array", or "this variable should always be evaluated as an integer", or "this variable is read-only and can not be re-set", or "this variable is exported (an environment variable)" etc.
The typeset built-in is a synonym for declare in bash, as typeset is used in other shells (ksh, where it originated, and zsh, for example) for setting variable attributes.